Skip to content

Commit 0248a20

Browse files
committed
feat: moved server_impl to infrastructure, created redislock
Signed-off-by: Martin <martin@hotmail.com.br>
1 parent fcbfdd4 commit 0248a20

File tree

9 files changed

+142
-30
lines changed

9 files changed

+142
-30
lines changed

benches/http_parse.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
2-
use httparse::{Header, ParserConfig, Request};
2+
use httparse::{ParserConfig, Request};
33
use rinha_de_backend::server_impl::server::parse_http;
44

55
const SAMPLE: &[u8] = b"GET /somepath HTTP/1.1\nHost: ifconfig.me\nUser-Agent: curl/8.5.0\nAccept: */*\nContent-Type: text/html; charset=ISO-8859-4\r\n\r\n{\"json_key\": 10}";

src/application/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
use redis::Client;
2+
use std::sync::Arc;
3+
4+
#[derive(Copy, Clone, Debug)]
15
pub struct AccountService {}
26

37
impl AccountService {
@@ -7,3 +11,8 @@ impl AccountService {
711
// and store transactions on redis so we can get to the balance faster
812
}
913
}
14+
15+
#[derive(Debug, Clone)]
16+
pub struct ServerData {
17+
pub redis: Arc<Client>,
18+
}

src/domain/transaction.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::server_impl::server::AnyResult;
1+
use crate::AnyResult;
22
use compact_str::CompactString;
33
use eyre::bail;
44
use serde::Serialize;

src/infrastructure/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod server_impl;
2+
3+
pub mod redis_lock;

src/infrastructure/redis_lock.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use compact_str::CompactString;
2+
use redis::{Client, Value};
3+
use std::iter::repeat_with;
4+
use std::sync::Arc;
5+
6+
#[derive(Debug, Clone)]
7+
pub struct RedisLock {
8+
rconn: Arc<Client>,
9+
/// A random alphanumeric string
10+
resource: CompactString,
11+
/// Value the lock is tied to
12+
val: i32,
13+
ttl_max: usize,
14+
}
15+
16+
#[derive(Debug, Clone)]
17+
pub struct RedisLockGuard<'a> {
18+
lock: &'a RedisLock,
19+
}
20+
21+
impl<'a> Drop for RedisLockGuard<'a> {
22+
fn drop(&mut self) {
23+
futures::executor::block_on(async { self.lock.unlock().await });
24+
}
25+
}
26+
27+
impl RedisLock {
28+
pub fn new(rconn: impl Into<Arc<Client>>, val: i32, ttl_max: usize) -> RedisLock {
29+
let resource: CompactString = repeat_with(fastrand::alphanumeric).take(5).collect();
30+
31+
Self {
32+
rconn: rconn.into(),
33+
resource,
34+
val,
35+
ttl_max,
36+
}
37+
}
38+
pub async fn acquire(&self) -> Result<RedisLockGuard, ()> {
39+
if lock(&self.rconn, &self.resource, self.val, self.ttl_max).await {
40+
Ok(RedisLockGuard { lock: self })
41+
} else {
42+
return Err(());
43+
}
44+
}
45+
46+
async fn unlock(&self) -> Result<(), ()> {
47+
let res = drop_lock(&self.rconn, &self.resource, self.val).await;
48+
Ok(())
49+
}
50+
}
51+
52+
async fn lock(redis: &Client, resource: &str, val: i32, ttl: usize) -> bool {
53+
let mut conn = redis.get_tokio_connection().await.unwrap();
54+
let result = redis::cmd("SET")
55+
.arg(resource)
56+
.arg(val)
57+
.arg("NX")
58+
.arg("PX")
59+
.arg(ttl)
60+
.query_async::<_, Value>(&mut conn)
61+
.await
62+
.unwrap();
63+
matches!(result, Value::Okay)
64+
}
65+
66+
async fn drop_lock(redis: &Client, resource: &str, val: i32) -> bool {
67+
let mut connection = redis.get_tokio_connection().await.unwrap();
68+
let script = redis::Script::new(DROP_SCRIPT);
69+
let res = script
70+
.key(resource)
71+
.arg(val)
72+
.invoke_async::<_, Value>(&mut connection)
73+
.await
74+
.unwrap();
75+
matches!(res, Value::Okay)
76+
}
77+
78+
const DROP_SCRIPT: &str = r#"if redis.call("get", KEYS[1]) == ARGV[1] then
79+
return redis.call("del", KEYS[1])
80+
else
81+
return 0
82+
end"#;
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
use crate::server_impl::server::{Header, Method};
2-
use ahash::AHashMap;
1+
use crate::infrastructure::server_impl::server::{Header, Method};
32
use enum_map::EnumMap;
43

54
#[derive(Debug)]

src/server_impl/response.rs renamed to src/infrastructure/server_impl/response.rs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
use crate::server_impl::server::Header;
1+
use crate::infrastructure::server_impl::server::Header;
22
use bytes::Bytes;
33
use compact_str::CompactString;
44
use fnv::FnvHashMap;
5+
use serde::Serialize;
56
use std::fmt::Write;
6-
use std::str::FromStr;
77
use strum::{EnumMessage, EnumString, IntoStaticStr};
88

99
#[allow(clippy::upper_case_acronyms, non_camel_case_types)]
@@ -36,17 +36,40 @@ impl From<StatusCode> for Response {
3636

3737
impl Response {
3838
pub fn into_http(self) -> Bytes {
39-
let mut buf = String::new();
39+
// FIXME: use BufWriter to avoid calling write multiple times
40+
let mut buf = String::with_capacity(80);
4041
let status_code: &str = self.status_code.into();
4142
let status_message = self.status_code.get_message().unwrap();
43+
4244
write!(
4345
buf,
44-
"HTTP/1.1 {status_code} {status_message}\n\
45-
Content-Type: application/json; charset-utf-8\
46-
Connection: keep-alive"
46+
"HTTP/1.1 {status_code} {status_message}\r\n\
47+
Content-Type: application/json; charset-utf-8\r\n\
48+
Connection: keep-alive\r\n"
4749
)
4850
.expect("No reason to fail.");
4951

52+
if let Some(body) = self.body {
53+
let length = body.len();
54+
write!(buf, "Content-Length: {length}\r\n\r\n{body}").unwrap();
55+
}
56+
5057
buf.into()
5158
}
5259
}
60+
61+
#[derive(Debug)]
62+
pub struct JsonResponse(pub(crate) Response);
63+
64+
impl JsonResponse {
65+
pub fn from<T>(body: T) -> Self
66+
where
67+
T: Serialize,
68+
{
69+
let mut response = Response::from(StatusCode::Ok);
70+
let body = simd_json::to_string(&body).ok();
71+
println!("body: {:?}", body);
72+
response.body = body;
73+
Self(response)
74+
}
75+
}
Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,42 @@
1-
use ahash::AHashMap;
21
use std::str::FromStr;
32
use std::sync::OnceLock;
43

4+
use crate::AnyResult;
55
use either::Either;
66
use enum_map::{Enum, EnumMap};
7-
use eyre::OptionExt;
8-
use fnv::FnvHashMap;
97
use httparse::{ParserConfig, Status};
108
use memchr::memchr;
119
use regex_lite::Regex;
1210
use strum::{EnumString, IntoStaticStr};
1311

1412
use crate::api::{statement_route, transaction_route};
15-
use crate::server_impl::request::Request;
16-
use crate::server_impl::response::{Response, StatusCode};
17-
18-
pub type AnyResult<T> = eyre::Result<T>;
13+
use crate::application::ServerData;
14+
use crate::infrastructure::server_impl::request::Request;
15+
use crate::infrastructure::server_impl::response::{Response, StatusCode};
1916

2017
static ROUTER: OnceLock<Regex> = OnceLock::new();
2118

2219
pub fn get_router() -> &'static Regex {
2320
ROUTER.get_or_init(|| Regex::new(r#"clientes/(\d)/(transacoes|extrato)"#).unwrap())
2421
}
2522

26-
pub fn process_server_request(buffer: &mut [u8], read_bytes: usize) -> AnyResult<Request> {
27-
let request = parse_http(buffer)?;
28-
29-
// let a = match_routes(request.resource.as_str(), request);
30-
31-
todo!()
32-
}
33-
34-
pub fn match_routes(resource: &str, request: Request) -> Either<Response, Response> {
35-
let Some(route) = get_router().captures(resource) else {
23+
pub fn match_routes(server_data: &ServerData, request: Request) -> Either<Response, Response> {
24+
let Some(route) = get_router().captures(request.resource) else {
3625
return Either::Right(StatusCode::NotFound.into());
3726
};
27+
let client_id = route
28+
.get(1)
29+
.and_then(|c| i32::from_str(c.as_str()).ok())
30+
.unwrap();
3831

3932
// the fastest router in existence!
4033
let response = match route.get(2).map(|c| c.as_str()).unwrap() {
41-
"transacao" => statement_route(request),
42-
"extrato" => transaction_route(request),
34+
"extrato" => statement_route(server_data, request, client_id),
35+
"transacao" => transaction_route(server_data, request, client_id),
4336
_ => unreachable!(),
4437
};
4538

46-
todo!()
39+
Either::Left(response.unwrap())
4740
}
4841

4942
#[allow(clippy::upper_case_acronyms, non_camel_case_types)]
@@ -54,6 +47,7 @@ pub enum Header {
5447
ACCEPT,
5548
CONTENT_TYPE,
5649
CONTENT_LENGTH,
50+
KEEP_ALIVE,
5751
}
5852

5953
impl FromStr for Header {
@@ -66,12 +60,14 @@ impl FromStr for Header {
6660
"Accept" => Self::ACCEPT,
6761
"Content-Type" => Self::CONTENT_TYPE,
6862
"Content-Length" => Self::CONTENT_LENGTH,
63+
"Keep-Alive" => Self::KEEP_ALIVE,
6964
_ => return Err(()),
7065
};
7166

7267
Ok(res)
7368
}
7469
}
70+
7571
#[allow(clippy::upper_case_acronyms, non_camel_case_types)]
7672
#[derive(Debug, Copy, Clone, PartialEq, EnumString, IntoStaticStr)]
7773
pub enum Method {

0 commit comments

Comments
 (0)