Compare commits

...

7 Commits

6 changed files with 115 additions and 117 deletions

81
Cargo.lock generated
View File

@ -57,15 +57,6 @@ dependencies = [
"syn",
]
[[package]]
name = "atomic"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c"
dependencies = [
"autocfg",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@ -250,20 +241,6 @@ dependencies = [
"instant",
]
[[package]]
name = "figment"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790b4292c72618abbab50f787a477014fe15634f96291de45672ce46afe122df"
dependencies = [
"atomic",
"pear",
"serde",
"serde_json",
"uncased",
"version_check",
]
[[package]]
name = "fnv"
version = "1.0.7"
@ -490,12 +467,6 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "inlinable_string"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb"
[[package]]
name = "instant"
version = "0.1.12"
@ -581,7 +552,6 @@ version = "0.1.0"
dependencies = [
"argh",
"base64",
"figment",
"redis",
"serde",
"serde_json",
@ -769,29 +739,6 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "pear"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15e44241c5e4c868e3eaa78b7c1848cadd6344ed4f54d029832d32b415a58702"
dependencies = [
"inlinable_string",
"pear_codegen",
"yansi",
]
[[package]]
name = "pear_codegen"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82a5ca643c2303ecb740d506539deba189e16f2754040a42901cd8105d0282d0"
dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn",
]
[[package]]
name = "percent-encoding"
version = "2.1.0"
@ -865,19 +812,6 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "proc-macro2-diagnostics"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada"
dependencies = [
"proc-macro2",
"quote",
"syn",
"version_check",
"yansi",
]
[[package]]
name = "quick-error"
version = "1.2.3"
@ -1363,15 +1297,6 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
[[package]]
name = "uncased"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0"
dependencies = [
"version_check",
]
[[package]]
name = "unicase"
version = "2.6.0"
@ -1593,9 +1518,3 @@ name = "windows_x86_64_msvc"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316"
[[package]]
name = "yansi"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"

View File

@ -8,7 +8,6 @@ edition = "2021"
[dependencies]
argh = "0.1.7"
base64 = "0.13.0"
figment = { version = "0.10.6", features = ["json", "env"] }
redis = { version = "~0.21.5", features = ["tokio-comp"] }
serde = { version = "~1.0.136", features = ["derive"] }
serde_json = "1.0.79"

View File

@ -1,12 +1,12 @@
# Create the build container to compile
FROM rust:latest as builder
# Prepare container
FROM rust:slim-buster
RUN USER=root cargo new --bin lonk
WORKDIR lonk
WORKDIR /lonk
# Compile dependencies
COPY ./Cargo.lock ./Cargo.lock
COPY ./Cargo.toml ./Cargo.toml
COPY ./Cargo.lock /lonk/Cargo.lock
COPY ./Cargo.toml /lonk/Cargo.toml
ARG PROFILE
@ -17,9 +17,10 @@ RUN rm src/*.rs
COPY ./src ./src
RUN rm ./target/${PROFILE:-release}/deps/lonk*
RUN cargo build
RUN cp /lonk/target/${PROFILE:-debug}/lonk /bin/lonk
# Execution container
FROM rust:latest
ARG PROFILE
COPY --from=builder /lonk/target/${PROFILE:-release}/lonk .
CMD ["./lonk"]
CMD ["./lonk"]

View File

@ -1,6 +1,6 @@
{
"db": {
"address": "redis://db",
"address": "redis://db:6379",
"worker_threads": 4
},
"slug_rules": {
@ -8,6 +8,12 @@
"chars": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"
},
"serve_rules": {
"Dir": "/data/served"
"dir": {
"Dir": "/data/served"
},
"addr": {
"ip": "127.0.0.1",
"port": 8080
}
}
}

View File

@ -1,4 +1,4 @@
version: '3.9'
version: "3.9"
services:
lonk:
build:
@ -9,6 +9,8 @@ services:
- LONK_CONFIG="/data/config.json"
volumes:
- ./data:/data
ports:
- 8080:8892
redis:
image: 'redis:alpine'
command: redis-server --save 20 1 --loglevel warning

View File

@ -1,8 +1,10 @@
use argh::FromArgs;
use figment::{providers::Format, Figment};
use core::panic;
use serde::{Deserialize, Serialize};
use std::{collections::BTreeSet, path::PathBuf, str::FromStr, sync::Arc, io::BufRead};
use tokio::{sync};
use std::{
collections::BTreeSet, io::BufRead, net::IpAddr, path::PathBuf, str::FromStr, sync::Arc,
};
use tokio::sync;
use validators::prelude::*;
use warp::{filters::BoxedFilter, hyper::StatusCode, Filter};
@ -20,6 +22,9 @@ macro_rules! clone_to_move {
clone_to_move!($x);
clone_to_move!($y)
};
(mut $x:ident) => {
let mut $x = $x.clone();
};
($x:ident) => {
let $x = $x.clone();
};
@ -34,14 +39,14 @@ struct Url {
#[derive(Deserialize, Serialize, Debug, Clone)]
struct DbConfig {
pub address: String,
pub worker_threads: usize,
address: String,
worker_threads: usize,
}
impl Default for DbConfig {
fn default() -> Self {
Self {
address: "redis://127.0.0.1".to_string(),
address: "redis://127.0.0.1:6379".to_string(),
worker_threads: 4,
}
}
@ -63,23 +68,54 @@ impl Default for SlugRules {
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub enum ServeRules {
pub enum ServeDirRules {
File(PathBuf),
Dir(PathBuf),
}
impl ServeRules {
impl ServeDirRules {
pub fn to_filter(&self) -> BoxedFilter<(warp::fs::File,)> {
match self {
ServeRules::File(file) => warp::fs::file(file.clone()).boxed(),
ServeRules::Dir(dir) => warp::fs::dir(dir.clone()).boxed(),
ServeDirRules::File(file) => warp::fs::file(file.clone()).boxed(),
ServeDirRules::Dir(dir) => warp::fs::dir(dir.clone()).boxed(),
}
}
}
impl Default for ServeDirRules {
fn default() -> Self {
ServeDirRules::Dir(PathBuf::from_str("/etc/lonk/served").unwrap())
}
}
#[derive(Serialize, Deserialize, Debug, Validator, Clone)]
#[validator(ip(local(Allow), port(Must)))]
struct ServeAddr {
ip: IpAddr,
port: u16,
}
impl Default for ServeAddr {
fn default() -> Self {
Self {
ip: [127, 0, 0, 1].into(),
port: 8080,
}
}
}
#[derive(Deserialize, Serialize, Debug, Clone)]
struct ServeRules {
dir: ServeDirRules,
addr: ServeAddr,
}
impl Default for ServeRules {
fn default() -> Self {
ServeRules::Dir(PathBuf::from_str("/etc/lonk/served").unwrap())
Self {
dir: Default::default(),
addr: ServeAddr::default(),
}
}
}
@ -130,24 +166,24 @@ impl SlugDatabase {
// I'm not sure this is the best way to implement this.
// (Alternatively: is there a better architecture?)
let rx = Arc::new(sync::Mutex::new(rx));
for _ in 0..worker_threads {
let mut connection = client.get_connection().expect("Could not open connection to Redis server.");
let mut connection = client
.get_connection()
.expect("Could not open connection to Redis server.");
clone_to_move!(rx);
tokio::spawn(async move {
while let Some(msg) = {(*rx.lock().await).recv().await} {
while let Some(msg) = { (*rx.lock().await).recv().await } {
match msg {
SlugDbMessage::Add(slug, url) => {
todo!()
},
}
}
}
});
}
SlugDatabase {
tx,
}
SlugDatabase { tx }
}
fn insert_slug(&self, slug: Slug, url: Url) -> Result<(), ()> {
@ -221,11 +257,45 @@ fn shorten(
#[tokio::main]
async fn serve() {
// Read configuration
let config_file = std::env::var("LONK_CONFIG").unwrap_or("lonk.json".to_string());
let config: Config = Figment::new()
.merge(figment::providers::Json::file(&config_file))
.extract()
.expect("Could not parse configuration file.");
let config: Config = {
let config_file_name = std::env::var("LONK_CONFIG").unwrap_or("lonk.json".to_string());
let config_file = std::fs::File::open(config_file_name.clone()).unwrap_or_else(|err| {
match err.kind() {
std::io::ErrorKind::NotFound => {
panic!("Configuration file {} does not exist.", config_file_name)
}
std::io::ErrorKind::PermissionDenied => {
panic!("Read permission to {} was denied.", config_file_name)
}
_ => panic!(
"Error when trying to read configuration file {}: {}",
config_file_name, err
),
};
});
let config_buf = std::io::BufReader::new(config_file);
serde_json::from_reader(config_buf).unwrap_or_else(|err| match err.classify() {
serde_json::error::Category::Io => panic!("IO error when reading configuration file."),
serde_json::error::Category::Syntax => panic!(
"Configuration file is syntactically incorrect.
See {}:line {}, column {}.",
&config_file_name,
err.line(),
err.column()
),
serde_json::error::Category::Data => panic!(
"Error deserializing configuration file; expected different data type.
See {}:line {}, column {}.",
&config_file_name,
err.line(),
err.column()
),
serde_json::error::Category::Eof => {
panic!("Unexpected end of file when reading configuration file.")
}
})
};
// Create slug factory
let slug_factory = Arc::new(SlugFactory::from_rules(config.slug_rules));
@ -233,12 +303,11 @@ async fn serve() {
// Initialize database
let db = {
let client = redis::Client::open(config.db.address).expect("Error opening Redis database.");
//let conn = Connection::open(config.db_location).expect("Could not open database.");
Arc::new(SlugDatabase::from_client(client, config.db.worker_threads))
};
// GET /
let homepage = warp::path::end().and(config.serve_rules.to_filter());
let homepage = warp::path::end().and(config.serve_rules.dir.to_filter());
// GET /shorten/:Base64WithoutPaddingUrl
let shorten = warp::path!("shorten" / Base64WithoutPaddingUrl).map({
@ -257,7 +326,9 @@ async fn serve() {
let routes = warp::get().and(homepage.or(shorten).or(link));
warp::serve(routes).run(([127, 0, 0, 1], 8892)).await;
warp::serve(routes)
.run((config.serve_rules.addr.ip, config.serve_rules.addr.port))
.await;
}
#[derive(FromArgs, PartialEq, Debug)]