rewrite URL shortener in Rust
This commit is contained in:
+1452
File diff suppressed because it is too large
Load Diff
+115
@@ -0,0 +1,115 @@
|
||||
use std::io::Read;
|
||||
use std::sync::Arc;
|
||||
use tiny_http::{Header, Response, Server, StatusCode};
|
||||
use ushort::{App, Config, RequestData};
|
||||
|
||||
fn main() {
|
||||
let arguments: Vec<String> = std::env::args().collect();
|
||||
if arguments.len() != 2 {
|
||||
eprintln!("Usage: {} <config.toml|config.json>", arguments[0]);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let config = Config::load(&arguments[1]).unwrap_or_else(|error| {
|
||||
eprintln!("Configuration error: {error}");
|
||||
std::process::exit(1);
|
||||
});
|
||||
let address = format!("{}:{}", config.host, config.port);
|
||||
let app = App::new(config).unwrap_or_else(|error| {
|
||||
eprintln!("Startup error: {error}");
|
||||
std::process::exit(1);
|
||||
});
|
||||
let server = Arc::new(Server::http(&address).unwrap_or_else(|error| {
|
||||
eprintln!("Failed to listen on {address}: {error}");
|
||||
std::process::exit(1);
|
||||
}));
|
||||
let shutdown_server = Arc::clone(&server);
|
||||
ctrlc::set_handler(move || shutdown_server.unblock()).unwrap_or_else(|error| {
|
||||
eprintln!("Failed to install signal handler: {error}");
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
eprintln!("URL Shortener listening on http://{address}");
|
||||
eprintln!("Base URL : {}", app.config.base_url);
|
||||
eprintln!(
|
||||
"Base path : {}",
|
||||
if app.config.base_path.is_empty() {
|
||||
"/"
|
||||
} else {
|
||||
&app.config.base_path
|
||||
}
|
||||
);
|
||||
eprintln!("DB path : {}", app.config.db_path.display());
|
||||
eprintln!(
|
||||
"Short codes : {}–{} chars",
|
||||
app.config.min_short_length, app.config.max_short_length
|
||||
);
|
||||
eprintln!("Retention days : {}", app.config.retention_days);
|
||||
eprintln!(
|
||||
"Rate limit : {} req/{}s per IP",
|
||||
app.config.rate_limit_requests, app.config.rate_limit_window
|
||||
);
|
||||
eprintln!("Production : {}", app.config.production);
|
||||
|
||||
for mut request in server.incoming_requests() {
|
||||
let maximum_body = app.max_request_body_bytes();
|
||||
let declared_too_large = request
|
||||
.body_length()
|
||||
.is_some_and(|length| length > maximum_body);
|
||||
let mut body = Vec::new();
|
||||
if !declared_too_large {
|
||||
let mut reader = request
|
||||
.as_reader()
|
||||
.take(maximum_body.saturating_add(1) as u64);
|
||||
if let Err(error) = reader.read_to_end(&mut body) {
|
||||
eprintln!("Failed to read request body: {error}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let body_too_large = declared_too_large || body.len() > maximum_body;
|
||||
let data = RequestData {
|
||||
method: request.method().as_str().to_string(),
|
||||
target: request.url().to_string(),
|
||||
headers: request
|
||||
.headers()
|
||||
.iter()
|
||||
.map(|header| {
|
||||
(
|
||||
header.field.as_str().to_string(),
|
||||
header.value.as_str().into(),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
body,
|
||||
remote_ip: request
|
||||
.remote_addr()
|
||||
.map(|address| address.ip().to_string())
|
||||
.unwrap_or_default(),
|
||||
};
|
||||
let client = data.remote_ip.clone();
|
||||
let method = data.method.clone();
|
||||
let target = data
|
||||
.target
|
||||
.split_once('?')
|
||||
.map_or_else(|| data.target.clone(), |(path, _)| path.to_string());
|
||||
let result = if body_too_large {
|
||||
ushort::ResponseData::payload_too_large()
|
||||
} else {
|
||||
app.handle(data)
|
||||
};
|
||||
let status = result.status;
|
||||
let mut response = Response::from_data(result.body).with_status_code(StatusCode(status));
|
||||
for (name, value) in result.headers {
|
||||
match Header::from_bytes(name.as_bytes(), value.as_bytes()) {
|
||||
Ok(header) => response.add_header(header),
|
||||
Err(()) => eprintln!("Skipped invalid response header {name}"),
|
||||
}
|
||||
}
|
||||
if let Err(error) = request.respond(response) {
|
||||
eprintln!("Failed to respond to {client}: {error}");
|
||||
} else {
|
||||
eprintln!("{client} - \"{method} {target}\" {status}");
|
||||
}
|
||||
}
|
||||
eprintln!("Shutting down.");
|
||||
}
|
||||
Reference in New Issue
Block a user