Implement the http variant of the bank server.

During that process, many shortcomings with the socket server and the
bank lib were fixed.

I am aware a massive commit like this is not ideal.
This commit is contained in:
Sebastian Hugentobler 2022-03-18 19:35:34 +01:00
parent c69654a924
commit dac95b7dae
Signed by: shu
GPG key ID: BB32CF3CA052C2F0
34 changed files with 1797 additions and 140 deletions

View file

@ -7,4 +7,5 @@ edition = "2021"
bank = { path = "../bank" }
anyhow = "1.0.55"
log = "0.4.14"
pretty_env_logger = "0.4.0"
pretty_env_logger = "0.4.0"
thiserror = "1.0.30"

View file

@ -12,7 +12,12 @@ use crate::protocol;
pub struct CloseAccount;
impl Command for CloseAccount {
fn execute(&self, bank: Arc<RwLock<Bank>>, data: &[u8], mut stream: &TcpStream) -> Result<usize> {
fn execute(
&self,
bank: Arc<RwLock<Bank>>,
data: &[u8],
mut stream: &TcpStream,
) -> Result<usize> {
debug!("account nr bytes {:?}", data);
let nr = String::from_utf8_lossy(data).to_string();
@ -25,9 +30,9 @@ impl Command for CloseAccount {
let mut acc = acc.write().unwrap();
stream.write(&protocol::account_passivate(acc.passivate()))?
}
None => stream.write(&protocol::account_passivate(false))?
None => stream.write(&protocol::account_passivate(false))?,
};
Ok(written)
}
}
}

View file

@ -12,7 +12,12 @@ use crate::protocol;
pub struct CreateAccount;
impl Command for CreateAccount {
fn execute(&self, bank: Arc<RwLock<Bank>>, data: &[u8], mut stream: &TcpStream) -> Result<usize> {
fn execute(
&self,
bank: Arc<RwLock<Bank>>,
data: &[u8],
mut stream: &TcpStream,
) -> Result<usize> {
debug!("owner nr bytes {:?}", data);
let owner = String::from_utf8_lossy(data);
@ -25,4 +30,4 @@ impl Command for CreateAccount {
let written = stream.write(&protocol::account_nr(&nr))?;
Ok(written)
}
}
}

View file

@ -12,7 +12,12 @@ use crate::protocol;
pub struct Deposit;
impl Command for Deposit {
fn execute(&self, bank: Arc<RwLock<Bank>>, data: &[u8], mut stream: &TcpStream) -> Result<usize> {
fn execute(
&self,
bank: Arc<RwLock<Bank>>,
data: &[u8],
mut stream: &TcpStream,
) -> Result<usize> {
let value_bytes: [u8; 8] = <[u8; 8]>::try_from(data[..8].to_vec().as_slice())?;
debug!("value bytes {:?}", value_bytes);
@ -30,9 +35,9 @@ impl Command for Deposit {
let mut acc = acc.write().unwrap();
stream.write(&protocol::deposit(acc.deposit(amount)))?
}
None => stream.write(&protocol::error(2))?
None => stream.write(&protocol::error(2))?,
};
Ok(written)
}
}
}

View file

@ -0,0 +1,35 @@
use std::fmt::{Display, Formatter};
use std::ops::Deref;
use thiserror::Error;
use bank::account::AccountError;
#[derive(Debug, Error)]
pub struct SocketAccountError(pub AccountError);
impl Display for SocketAccountError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.deref())
}
}
impl Deref for SocketAccountError {
type Target = AccountError;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<SocketAccountError> for u8 {
fn from(e: SocketAccountError) -> Self {
match e.deref() {
AccountError::Overdraw => 11,
AccountError::Inactive => 12,
AccountError::InvalidAmount => 13,
AccountError::NotFound => 14,
AccountError::AccountNotZero => 15,
}
}
}

View file

@ -12,9 +12,14 @@ use crate::protocol;
pub struct Fail;
impl Command for Fail {
fn execute(&self, _: Arc<RwLock<Bank>>, error_code: &[u8], mut stream: &TcpStream) -> Result<usize> {
fn execute(
&self,
_: Arc<RwLock<Bank>>,
error_code: &[u8],
mut stream: &TcpStream,
) -> Result<usize> {
error!("sending error code {}", error_code[0]);
let written = stream.write(&protocol::error(error_code[0]))?;
Ok(written)
}
}
}

View file

@ -12,7 +12,12 @@ use crate::protocol;
pub struct GetAccount;
impl Command for GetAccount {
fn execute(&self, bank: Arc<RwLock<Bank>>, data: &[u8], mut stream: &TcpStream) -> Result<usize> {
fn execute(
&self,
bank: Arc<RwLock<Bank>>,
data: &[u8],
mut stream: &TcpStream,
) -> Result<usize> {
debug!("account nr bytes {:?}", data);
let nr = String::from_utf8_lossy(data).to_string();
@ -25,9 +30,9 @@ impl Command for GetAccount {
let acc = acc.read().unwrap();
stream.write(&protocol::account(&acc))?
}
None => stream.write(&protocol::error(2))?
None => stream.write(&protocol::error(2))?,
};
Ok(written)
}
}
}

View file

@ -16,12 +16,10 @@ impl Command for GetAccountNrs {
info!("getting account numbers...");
let bank = bank.read().unwrap();
let nrs: Vec<String> = bank.account_numbers()
.into_iter()
.collect();
let nrs: Vec<String> = bank.account_numbers().into_iter().collect();
let written = stream.write(&protocol::account_nrs(&nrs))?;
Ok(written)
}
}
}

View file

@ -4,7 +4,6 @@ use std::sync::{Arc, RwLock};
use anyhow::Result;
use bank::account::AccountError;
use bank::bank::Bank;
use crate::commands::close_account::CloseAccount;
@ -16,13 +15,14 @@ use crate::commands::get_account_nrs::GetAccountNrs;
use crate::commands::pong::Pong;
use crate::commands::withdraw::Withdraw;
mod pong;
mod fail;
mod close_account;
mod create_account;
mod deposit;
pub mod error;
mod fail;
mod get_account;
mod get_account_nrs;
mod pong;
mod withdraw;
pub trait Command: Sync + Send {
@ -33,26 +33,6 @@ pub struct Commands {
cmds: HashMap<u8, Box<dyn Command>>,
}
struct WebAccountError(AccountError);
impl From<&WebAccountError> for u8 {
fn from(error: &WebAccountError) -> Self {
match error.0 {
AccountError::Overdraw() => 11,
AccountError::Inactive() => 12,
AccountError::InvalidAmount() => 13,
}
}
}
pub fn error_to_code(error: &AccountError) -> u8 {
match error {
AccountError::Overdraw() => 11,
AccountError::Inactive() => 12,
AccountError::InvalidAmount() => 13,
}
}
impl Commands {
pub fn new() -> Self {
let mut cmds = HashMap::new();
@ -64,15 +44,19 @@ impl Commands {
cmds.insert(6_u8, Box::new(Deposit) as Box<dyn Command>);
cmds.insert(7_u8, Box::new(Withdraw) as Box<dyn Command>);
Self {
cmds
}
Self { cmds }
}
pub fn run(&self, bank: Arc<RwLock<Bank>>, cmd: u8, data: &[u8], stream: &TcpStream) -> Result<usize> {
pub fn run(
&self,
bank: Arc<RwLock<Bank>>,
cmd: u8,
data: &[u8],
stream: &TcpStream,
) -> Result<usize> {
match self.cmds.get(&cmd) {
None => Fail.execute(bank, &[1_u8], stream),
Some(cmd) => cmd.execute(bank, data, stream)
Some(cmd) => cmd.execute(bank, data, stream),
}
}
}

View file

@ -17,4 +17,4 @@ impl Command for Pong {
let written = stream.write(&[protocol::PONG])?;
Ok(written)
}
}
}

View file

@ -12,7 +12,12 @@ use crate::protocol;
pub struct Withdraw;
impl Command for Withdraw {
fn execute(&self, bank: Arc<RwLock<Bank>>, data: &[u8], mut stream: &TcpStream) -> Result<usize> {
fn execute(
&self,
bank: Arc<RwLock<Bank>>,
data: &[u8],
mut stream: &TcpStream,
) -> Result<usize> {
let value_bytes: [u8; 8] = <[u8; 8]>::try_from(data[..8].to_vec().as_slice())?;
debug!("value bytes {:?}", value_bytes);
@ -24,15 +29,14 @@ impl Command for Withdraw {
info!("withdrawing {} from {}...", amount, nr);
let bank = bank.read().unwrap();
let written = match bank.accounts.get(&nr) {
Some(acc) => {
let mut acc = acc.write().unwrap();
stream.write(&protocol::withdraw(acc.withdraw(amount)))?
}
None => stream.write(&protocol::error(2))?
None => stream.write(&protocol::error(2))?,
};
Ok(written)
}
}
}

View file

@ -1,10 +1,10 @@
#[macro_use]
extern crate log;
mod commands;
mod protocol;
mod server;
mod threadpool;
mod commands;
fn main() {
pretty_env_logger::init();

View file

@ -2,18 +2,11 @@ use anyhow::Result;
use bank::account::{Account, AccountError};
use crate::commands::error_to_code;
use crate::commands::error::SocketAccountError;
pub const START: [u8; 2] = [0xde, 0xad];
pub const PONG: u8 = 0b0010_0000;
fn to_error_code(error: anyhow::Error, default: u8) -> u8 {
match error.root_cause().downcast_ref::<AccountError>() {
Some(e) => error_to_code(e),
None => default,
}
}
pub fn account_nr(nr: &str) -> Vec<u8> {
let mut response = vec![PONG];
response.append(&mut nr.as_bytes().to_vec());
@ -26,17 +19,17 @@ pub fn account_passivate(was_passivated: bool) -> Vec<u8> {
vec![PONG | is_active_byte]
}
pub fn deposit(result: Result<()>) -> Vec<u8> {
pub fn deposit(result: Result<f64, AccountError>) -> Vec<u8> {
match result {
Err(e) => error(to_error_code(e, 10)).to_vec(),
Ok(_) => vec![PONG]
Err(e) => error(SocketAccountError(e).into()).to_vec(),
Ok(_) => vec![PONG],
}
}
pub fn withdraw(result: Result<()>) -> Vec<u8> {
pub fn withdraw(result: Result<f64, AccountError>) -> Vec<u8> {
match result {
Err(e) => error(to_error_code(e, 10)).to_vec(),
Ok(_) => vec![PONG]
Err(e) => error(SocketAccountError(e).into()).to_vec(),
Ok(_) => vec![PONG],
}
}
@ -85,4 +78,4 @@ pub fn account(account: &Account) -> Vec<u8> {
pub fn error(code: u8) -> [u8; 1] {
[0b0100_0000 | code]
}
}

View file

@ -33,7 +33,11 @@ pub fn run(host: &str, threads: usize) -> Result<()> {
Ok(())
}
fn handle_connection(bank: Arc<RwLock<Bank>>, cmds: &Commands, mut stream: TcpStream) -> Result<()> {
fn handle_connection(
bank: Arc<RwLock<Bank>>,
cmds: &Commands,
mut stream: TcpStream,
) -> Result<()> {
const BUF_SIZE: usize = 64;
let mut data: Vec<u8> = vec![];
@ -58,4 +62,3 @@ fn handle_connection(bank: Arc<RwLock<Bank>>, cmds: &Commands, mut stream: TcpSt
Ok(())
}

View file

@ -1,5 +1,5 @@
use std::sync::Arc;
use std::sync::mpsc;
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;
@ -33,8 +33,8 @@ impl ThreadPool {
}
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
where
F: FnOnce() + Send + 'static,
{
let job = Box::new(f);
@ -91,4 +91,4 @@ impl Worker {
thread: Some(thread),
}
}
}
}