Compare commits
2 Commits
1c42b86f90
...
44472bef1e
Author | SHA1 | Date | |
---|---|---|---|
44472bef1e | |||
f1021295f7 |
89
Cargo.lock
generated
89
Cargo.lock
generated
@ -71,6 +71,15 @@ dependencies = [
|
|||||||
"opaque-debug",
|
"opaque-debug",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "android-tzdata"
|
name = "android-tzdata"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@ -717,6 +726,19 @@ dependencies = [
|
|||||||
"syn 2.0.87",
|
"syn 2.0.87",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
|
||||||
|
dependencies = [
|
||||||
|
"humantime",
|
||||||
|
"is-terminal",
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
"termcolor",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "erased-serde"
|
name = "erased-serde"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
@ -1114,6 +1136,12 @@ version = "1.9.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
|
checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "humantime"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iana-time-zone"
|
name = "iana-time-zone"
|
||||||
version = "0.1.61"
|
version = "0.1.61"
|
||||||
@ -1313,6 +1341,17 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is-terminal"
|
||||||
|
version = "0.4.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi 0.5.2",
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.11"
|
version = "1.0.11"
|
||||||
@ -1795,6 +1834,35 @@ dependencies = [
|
|||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "route-recognizer"
|
name = "route-recognizer"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@ -1866,9 +1934,10 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sdk_storage"
|
name = "sdk_storage"
|
||||||
version = "0.1.0"
|
version = "0.2.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
|
"env_logger",
|
||||||
"hex",
|
"hex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -2270,6 +2339,15 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.69"
|
version = "1.0.69"
|
||||||
@ -2619,6 +2697,15 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-x86_64-pc-windows-gnu"
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -9,6 +9,7 @@ async-std = { version = "1", features = ["attributes"] }
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
|
env_logger = "0.10"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
42
src/lib.rs
42
src/lib.rs
@ -4,7 +4,7 @@ use async_std::path::Path;
|
|||||||
use async_std::stream::StreamExt;
|
use async_std::stream::StreamExt;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
use tide::{StatusCode, Request, Response};
|
use tide::{log, Request, Response, StatusCode};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct StorageService {
|
pub struct StorageService {
|
||||||
@ -143,7 +143,7 @@ pub fn unix_to_system_time(unix_timestamp: u64) -> SystemTime {
|
|||||||
UNIX_EPOCH + Duration::from_secs(unix_timestamp)
|
UNIX_EPOCH + Duration::from_secs(unix_timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct StoreRequest {
|
pub struct StoreRequest {
|
||||||
pub key: String,
|
pub key: String,
|
||||||
pub value: String,
|
pub value: String,
|
||||||
@ -163,22 +163,23 @@ pub async fn handle_health(_req: Request<StorageService>) -> tide::Result<Respon
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_store(mut req: Request<StorageService>, no_ttl_permanent: bool) -> tide::Result<Response> {
|
pub async fn handle_store(mut req: Request<StorageService>, no_ttl_permanent: bool) -> tide::Result<Response> {
|
||||||
let data: StoreRequest = match req.body_json().await {
|
// Extract key from URL parameter
|
||||||
Ok(data) => data,
|
let key: String = req.param("key")?.to_string();
|
||||||
Err(e) => {
|
|
||||||
return Ok(Response::builder(StatusCode::BadRequest)
|
|
||||||
.body(format!("Invalid request: {}", e))
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if data.key.len() != 64 || !data.key.chars().all(|c| c.is_ascii_hexdigit()) {
|
// Validate key format
|
||||||
|
if key.len() != 64 || !key.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||||
return Ok(Response::builder(StatusCode::BadRequest)
|
return Ok(Response::builder(StatusCode::BadRequest)
|
||||||
.body("Invalid key: must be a 32 bytes hex string.".to_string())
|
.body("Invalid key: must be a 32 bytes hex string.".to_string())
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
let live_for: Option<Duration> = if let Some(ttl) = data.ttl {
|
// Get TTL from query parameter (optional)
|
||||||
|
let ttl: Option<u64> = req.url().query_pairs()
|
||||||
|
.find(|(key, _)| key == "ttl")
|
||||||
|
.and_then(|(_, value)| value.parse().ok());
|
||||||
|
log::info!("ttl: {:?}", ttl);
|
||||||
|
|
||||||
|
let live_for: Option<Duration> = if let Some(ttl) = ttl {
|
||||||
if ttl < 60 {
|
if ttl < 60 {
|
||||||
return Ok(Response::builder(StatusCode::BadRequest)
|
return Ok(Response::builder(StatusCode::BadRequest)
|
||||||
.body(format!("Invalid ttl: must be at least {} seconds.", 60))
|
.body(format!("Invalid ttl: must be at least {} seconds.", 60))
|
||||||
@ -204,17 +205,20 @@ pub async fn handle_store(mut req: Request<StorageService>, no_ttl_permanent: bo
|
|||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let value_bytes = match hex::decode(&data.value) {
|
// Read binary data directly from request body
|
||||||
Ok(value) => value,
|
let value_bytes = match req.body_bytes().await {
|
||||||
|
Ok(bytes) => bytes,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Ok(Response::builder(StatusCode::BadRequest)
|
return Ok(Response::builder(StatusCode::BadRequest)
|
||||||
.body(format!("Invalid request: {}", e))
|
.body(format!("Failed to read request body: {}", e))
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
log::info!("received {} bytes", value_bytes.len());
|
||||||
|
|
||||||
let svc = req.state();
|
let svc = req.state();
|
||||||
match svc.store_data(&data.key, &value_bytes, expires_at).await {
|
match svc.store_data(&key, &value_bytes, expires_at).await {
|
||||||
Ok(()) => Ok(Response::builder(StatusCode::Ok)
|
Ok(()) => Ok(Response::builder(StatusCode::Ok)
|
||||||
.body(serde_json::to_value(&ApiResponse {
|
.body(serde_json::to_value(&ApiResponse {
|
||||||
message: "Data stored successfully.".to_string(),
|
message: "Data stored successfully.".to_string(),
|
||||||
@ -240,9 +244,9 @@ pub async fn handle_retrieve(req: Request<StorageService>) -> tide::Result<Respo
|
|||||||
let svc = req.state();
|
let svc = req.state();
|
||||||
match svc.retrieve_data(&key).await {
|
match svc.retrieve_data(&key).await {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
let encoded_value = hex::encode(value);
|
|
||||||
Ok(Response::builder(StatusCode::Ok)
|
Ok(Response::builder(StatusCode::Ok)
|
||||||
.body(serde_json::to_value(&RetrieveResponse { key, value: encoded_value })?)
|
.header("Content-Type", "application/octet-stream")
|
||||||
|
.body(value)
|
||||||
.build())
|
.build())
|
||||||
}
|
}
|
||||||
Err(e) => Ok(Response::builder(StatusCode::NotFound).body(e).build()),
|
Err(e) => Ok(Response::builder(StatusCode::NotFound).body(e).build()),
|
||||||
@ -253,7 +257,7 @@ pub fn create_app(no_ttl_permanent: bool, storage_dir: impl Into<String>) -> tid
|
|||||||
let svc = StorageService::new(storage_dir);
|
let svc = StorageService::new(storage_dir);
|
||||||
let mut app = tide::with_state(svc);
|
let mut app = tide::with_state(svc);
|
||||||
app.at("/health").get(handle_health);
|
app.at("/health").get(handle_health);
|
||||||
app.at("/store").post(move |req| handle_store(req, no_ttl_permanent));
|
app.at("/store/:key").post(move |req| handle_store(req, no_ttl_permanent));
|
||||||
app.at("/retrieve/:key").get(handle_retrieve);
|
app.at("/retrieve/:key").get(handle_retrieve);
|
||||||
app
|
app
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,18 @@ use std::env;
|
|||||||
use async_std::task;
|
use async_std::task;
|
||||||
use async_std::fs::create_dir_all;
|
use async_std::fs::create_dir_all;
|
||||||
use sdk_storage::{StorageService, create_app};
|
use sdk_storage::{StorageService, create_app};
|
||||||
|
use tide::log;
|
||||||
|
|
||||||
const STORAGE_DIR: &str = "./storage";
|
const STORAGE_DIR: &str = "./storage";
|
||||||
const PORT: u16 = 8081;
|
const PORT: u16 = 8080;
|
||||||
const DEFAULT_TTL: u64 = 86400;
|
const DEFAULT_TTL: u64 = 86400;
|
||||||
|
|
||||||
|
|
||||||
#[async_std::main]
|
#[async_std::main]
|
||||||
async fn main() -> tide::Result<()> {
|
async fn main() -> tide::Result<()> {
|
||||||
|
// Initialize logging
|
||||||
|
env_logger::init();
|
||||||
|
log::info!("Starting server");
|
||||||
// Parse command line arguments
|
// Parse command line arguments
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
let no_ttl_permanent = args.iter().any(|arg| arg == "--permanent");
|
let no_ttl_permanent = args.iter().any(|arg| arg == "--permanent");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user