Implement in-memory database with HTTP interface for item management
This commit is contained in:
@@ -0,0 +1,182 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::net::{TcpListener, TcpStream};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
struct InMemSetDB {
|
||||||
|
data: Arc<Mutex<HashSet<String>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InMemSetDB {
|
||||||
|
fn new() -> Self {
|
||||||
|
InMemSetDB {
|
||||||
|
data: Arc::new(Mutex::new(HashSet::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(&self, item: String) -> String {
|
||||||
|
let mut set = self.data.lock().unwrap();
|
||||||
|
set.insert(item);
|
||||||
|
r#"{"success":true}"#.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains(&self, item: &str) -> String {
|
||||||
|
let set = self.data.lock().unwrap();
|
||||||
|
let exists = set.contains(item);
|
||||||
|
format!(r#"{{"exists":{}}}"#, exists)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&self) -> String {
|
||||||
|
let mut set = self.data.lock().unwrap();
|
||||||
|
set.clear();
|
||||||
|
r#"{"success":true}"#.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> String {
|
||||||
|
let set = self.data.lock().unwrap();
|
||||||
|
format!(r#"{{"size":{}}}"#, set.len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_request(request: &str) -> Option<(String, String, Option<String>)> {
|
||||||
|
let lines: Vec<&str> = request.split("\r\n").collect();
|
||||||
|
if lines.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parts: Vec<&str> = lines[0].split_whitespace().collect();
|
||||||
|
if parts.len() < 2 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let method = parts[0].to_string();
|
||||||
|
let path = parts[1].to_string();
|
||||||
|
|
||||||
|
let body = if method == "POST" {
|
||||||
|
if let Some(idx) = request.find("\r\n\r\n") {
|
||||||
|
Some(request[idx + 4..].to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Some((method, path, body))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_json_field(json: &str, field: &str) -> Option<String> {
|
||||||
|
let search = format!(r#""{}":""#, field);
|
||||||
|
if let Some(start) = json.find(&search) {
|
||||||
|
let value_start = start + search.len();
|
||||||
|
if let Some(end) = json[value_start..].find('"') {
|
||||||
|
return Some(json[value_start..value_start + end].to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_request(
|
||||||
|
set: &InMemSetDB,
|
||||||
|
method: &str,
|
||||||
|
path: &str,
|
||||||
|
body: Option<String>,
|
||||||
|
) -> (u16, String) {
|
||||||
|
match (method, path) {
|
||||||
|
("POST", "/add") => {
|
||||||
|
if let Some(body_str) = body {
|
||||||
|
if let Some(item) = extract_json_field(&body_str, "item") {
|
||||||
|
let response = set.add(item);
|
||||||
|
return (200, response);
|
||||||
|
}
|
||||||
|
return (400, r#"{"error":"Missing item"}"#.to_string());
|
||||||
|
}
|
||||||
|
(400, r#"{"error":"Missing body"}"#.to_string())
|
||||||
|
}
|
||||||
|
("POST", "/contains") => {
|
||||||
|
if let Some(body_str) = body {
|
||||||
|
if let Some(item) = extract_json_field(&body_str, "item") {
|
||||||
|
let response = set.contains(&item);
|
||||||
|
return (200, response);
|
||||||
|
}
|
||||||
|
return (400, r#"{"error":"Missing item"}"#.to_string());
|
||||||
|
}
|
||||||
|
(400, r#"{"error":"Missing body"}"#.to_string())
|
||||||
|
}
|
||||||
|
("POST", "/clear") => {
|
||||||
|
let response = set.clear();
|
||||||
|
(200, response)
|
||||||
|
}
|
||||||
|
("GET", "/size") => {
|
||||||
|
let response = set.size();
|
||||||
|
(200, response)
|
||||||
|
}
|
||||||
|
_ => (404, r#"{"error":"Not found"}"#.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_response(status: u16, body: String) -> String {
|
||||||
|
let status_text = match status {
|
||||||
|
200 => "OK",
|
||||||
|
400 => "Bad Request",
|
||||||
|
404 => "Not Found",
|
||||||
|
_ => "Unknown",
|
||||||
|
};
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"HTTP/1.1 {} {}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
|
||||||
|
status, status_text, body.len(), body
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_client(mut stream: TcpStream, set: InMemSetDB) {
|
||||||
|
let mut buffer = [0; 4096];
|
||||||
|
|
||||||
|
match stream.read(&mut buffer) {
|
||||||
|
Ok(size) => {
|
||||||
|
if size == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let request = String::from_utf8_lossy(&buffer[..size]);
|
||||||
|
|
||||||
|
if let Some((method, path, body)) = parse_request(&request) {
|
||||||
|
let (status, response_body) = handle_request(&set, &method, &path, body);
|
||||||
|
let response = build_response(status, response_body);
|
||||||
|
|
||||||
|
let _ = stream.write_all(response.as_bytes());
|
||||||
|
let _ = stream.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => eprintln!("Error reading from stream: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let listener = TcpListener::bind("127.0.0.1:8080").expect("Failed to bind");
|
||||||
|
let set = InMemSetDB::new();
|
||||||
|
|
||||||
|
println!("InMemSetDB started on http://127.0.0.1:8080");
|
||||||
|
println!("\nEndpoints:");
|
||||||
|
println!(r#" POST /add - {{"item": "value"}}"#);
|
||||||
|
println!(r#" POST /contains - {{"item": "value"}}"#);
|
||||||
|
println!(" POST /clear");
|
||||||
|
println!(" GET /size");
|
||||||
|
println!("\nWaiting for connections...\n");
|
||||||
|
|
||||||
|
for stream in listener.incoming() {
|
||||||
|
match stream {
|
||||||
|
Ok(stream) => {
|
||||||
|
let set_clone = InMemSetDB {
|
||||||
|
data: Arc::clone(&set.data),
|
||||||
|
};
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
handle_client(stream, set_clone);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(e) => eprintln!("Connection failed: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import socket
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
def fast_http_request(method, path, body=None):
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
sock.connect(("127.0.0.1", 8080))
|
||||||
|
|
||||||
|
if body:
|
||||||
|
body_str = body
|
||||||
|
request = f"{method} {path} HTTP/1.1\r\nHost: localhost\r\nContent-Length: {len(body_str)}\r\n\r\n{body_str}"
|
||||||
|
else:
|
||||||
|
request = f"{method} {path} HTTP/1.1\r\nHost: localhost\r\n\r\n"
|
||||||
|
|
||||||
|
sock.send(request.encode())
|
||||||
|
response = sock.recv(4096).decode()
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def get_body_from_response(response):
|
||||||
|
return json.loads(response.split("\r\n\r\n", 1)[1])
|
||||||
|
|
||||||
|
|
||||||
|
def add_item(item):
|
||||||
|
body = f'{{"item":"{item}"}}'
|
||||||
|
response = fast_http_request("POST", "/add", body)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def clear_items():
|
||||||
|
response = fast_http_request("POST", "/clear")
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def contains_item(item):
|
||||||
|
body = f'{{"item":"{item}"}}'
|
||||||
|
response = fast_http_request("POST", "/contains", body)
|
||||||
|
body = get_body_from_response(response)
|
||||||
|
return body["exists"]
|
||||||
|
|
||||||
|
|
||||||
|
def size():
|
||||||
|
response = fast_http_request("GET", "/size")
|
||||||
|
body = get_body_from_response(response)
|
||||||
|
return int(body["size"])
|
||||||
Reference in New Issue
Block a user