Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "hteapot"
version = "0.6.2"
version = "0.6.5"
edition = "2024"
authors = ["Alb Ruiz G. <[email protected]>"]
description = "HTeaPot is a lightweight HTTP server library designed to be easy to use and extend."
Expand All @@ -25,6 +25,7 @@ path = "src/hteapot/mod.rs"
name = "hteapot"

[dependencies]
[target.'cfg(unix)'.dependencies]
libc = "0.2.172"


Expand Down
19 changes: 19 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM rust AS builder

WORKDIR /app
COPY Cargo.lock Cargo.lock
COPY Cargo.toml Cargo.toml
COPY src ./src

RUN cargo build --release

FROM ubuntu

COPY --from=builder /app/target/release/hteapot /bin/hteapot

EXPOSE 80

WORKDIR /config

ENTRYPOINT ["/bin/hteapot"]
CMD ["config.toml"]
18 changes: 18 additions & 0 deletions examples/basic2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use hteapot::{Hteapot, HttpRequest, HttpResponse, HttpStatus};

fn main() {
let server = Hteapot::new("localhost", 8081);
server.listen(move |req: HttpRequest| {
// This will be executed for each request
let body = String::from_utf8(req.body).unwrap_or("NOPE".to_string());
for header in req.headers {
println!("- {}: {}", header.0, header.1);
}
println!("{}", body);
HttpResponse::new(
HttpStatus::IAmATeapot,
format!("Hello, I am HTeaPot\n{}", body),
None,
)
});
}
28 changes: 28 additions & 0 deletions examples/proxy_con.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use hteapot::{Hteapot, HttpMethod, HttpRequest, HttpResponse, TunnelResponse};

fn main() {
let server = Hteapot::new_threaded("0.0.0.0", 8081, 3);
server.listen(move |req: HttpRequest| {
println!("New request to {} {}!", req.method.to_str(), &req.path);
if req.method == HttpMethod::CONNECT {
TunnelResponse::new(&req.path)
} else {
println!("{:?}", req);
let addr = req.headers.get("host");
let addr = if let Some(addr) = addr {
addr
} else {
return HttpResponse::new(
hteapot::HttpStatus::InternalServerError,
"content",
None,
);
};
req.brew(addr).unwrap_or(HttpResponse::new(
hteapot::HttpStatus::InternalServerError,
"content",
None,
))
}
});
}
17 changes: 11 additions & 6 deletions src/cache.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// Written by Alberto Ruiz, 2024-11-05
//
//
// Config module: handles application configuration setup and parsing.
// This module defines structs and functions to load and validate
// configuration settings from files, environment variables, or other sources.

use std::collections::HashMap;
use std::hash::Hash;
use std::time;
use std::time::SystemTime;

Expand All @@ -24,14 +25,18 @@ use std::time::SystemTime;
/// let data = cache.get("hello".into());
/// assert!(data.is_some());
/// ```
pub struct Cache {
pub struct Cache<K, V> {
// TODO: consider make it generic
// The internal store: (data, expiration timestamp)
data: HashMap<String, (Vec<u8>, u64)>,
data: HashMap<K, (V, u64)>,
max_ttl: u64,
}

impl Cache {
impl<K, V> Cache<K, V>
where
K: Eq + Hash,
V: Clone,
{
/// Creates a new `Cache` with the specified TTL in seconds.
pub fn new(max_ttl: u64) -> Self {
Cache {
Expand Down Expand Up @@ -61,14 +66,14 @@ impl Cache {
}

/// Stores data in the cache with the given key and a TTL.
pub fn set(&mut self, key: String, data: Vec<u8>) {
pub fn set(&mut self, key: K, data: V) {
self.data.insert(key, (data, self.get_ttl()));
}

/// Retrieves data from the cache if it exists and hasn't expired.
///
/// Removes and returns `None` if the TTL has expired.
pub fn get(&mut self, key: String) -> Option<Vec<u8>> {
pub fn get(&mut self, key: &K) -> Option<V> {
let r = self.data.get(&key);
if r.is_some() {
let (data, ttl) = r.unwrap();
Expand Down
80 changes: 62 additions & 18 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Written by Alberto Ruiz 2024-04-07 (Happy 3th monthsary)
//
//
// This is the config module: responsible for loading application configuration
// from a file and providing structured access to settings.

use std::{any::Any, collections::HashMap, fs};
use std::{any::Any, collections::HashMap, fs, path::Path};

/// Dynamic TOML value representation.
///
Expand Down Expand Up @@ -62,7 +62,7 @@ pub fn toml_parser(content: &str) -> HashMap<String, TOMLSchema> {
let mut map = HashMap::new();
let mut submap = HashMap::new();
let mut title = "".to_string();

let lines = content.split("\n");
for line in lines {
if line.starts_with("#") || line.is_empty() {
Expand All @@ -88,13 +88,13 @@ pub fn toml_parser(content: &str) -> HashMap<String, TOMLSchema> {
submap = HashMap::new();
continue;
}

// Split key and value
let parts = line.split("=").collect::<Vec<&str>>();
if parts.len() != 2 {
continue;
}

// Remove leading and trailing whitespace
let key = parts[0]
.trim()
Expand All @@ -103,7 +103,7 @@ pub fn toml_parser(content: &str) -> HashMap<String, TOMLSchema> {
if key.is_empty() {
continue;
}

// Remove leading and trailing whitespace
let value = parts[1].trim();
let value = if value.contains('\'') || value.contains('"') {
Expand Down Expand Up @@ -152,14 +152,14 @@ pub fn toml_parser(content: &str) -> HashMap<String, TOMLSchema> {
/// such as host, port, caching behavior, and proxy rules.
#[derive(Debug)]
pub struct Config {
pub port: u16, // Port number to listen
pub host: String, // Host name or IP
pub root: String, // Root directory to serve files
pub port: u16, // Port number to listen
pub host: String, // Host name or IP
pub root: String, // Root directory to serve files
pub cache: bool,
pub cache_ttl: u16,
pub threads: u16,
pub log_file: Option<String>,
pub index: String, // Index file to serve by default
pub index: String, // Index file to serve by default
// pub error: String, // Error file to serve when a file is not found
pub proxy_rules: HashMap<String, String>,
}
Expand Down Expand Up @@ -192,6 +192,35 @@ impl Config {
}
}

pub fn new_serve(path: &str) -> Config {
let mut s_path = "./".to_string();
s_path.push_str(path);
let serving_path = Path::new(&s_path);
let file_name: &str;
let root_dir: String;
if serving_path.is_file() {
let parent_path = serving_path.parent().unwrap();
root_dir = parent_path.to_str().unwrap().to_string();
file_name = serving_path.file_name().unwrap().to_str().unwrap();
} else {
file_name = "index.html";
root_dir = serving_path.to_str().unwrap().to_string();
};

Config {
port: 8080,
host: "0.0.0.0".to_string(),
root: root_dir,
index: file_name.to_string(),
log_file: None,

threads: 1,
cache: false,
cache_ttl: 0,
proxy_rules: HashMap::new(),
}
}

/// Loads configuration from a TOML file, returning defaults on failure.
///
/// Expects the file to contain `[HTEAPOT]` and optionally `[proxy]` sections.
Expand Down Expand Up @@ -224,13 +253,13 @@ impl Config {

// Suggested alternative parsing logic
// if let Some(proxy_map) = map.get("proxy") {
// for k in proxy_map.keys() {
// if let Some(url) = proxy_map.get2(k) {
// proxy_rules.insert(k.clone(), url);
// } else {
// println!("Missing or invalid proxy URL for key: {}", k);
// }
// }
// for k in proxy_map.keys() {
// if let Some(url) = proxy_map.get2(k) {
// proxy_rules.insert(k.clone(), url);
// } else {
// println!("Missing or invalid proxy URL for key: {}", k);
// }
// }
// }

// Extract main configuration
Expand All @@ -239,7 +268,6 @@ impl Config {
// Suggested alternative parsing logic (Not working)
// let map = map.get("HTEAPOT").unwrap_or(&TOMLSchema::new());


Config {
port: map.get2("port").unwrap_or(8080),
host: map.get2("host").unwrap_or("".to_string()),
Expand All @@ -253,4 +281,20 @@ impl Config {
proxy_rules,
}
}

pub fn new_proxy() -> Config {
let mut proxy_rules = HashMap::new();
proxy_rules.insert("/".to_string(), "".to_string());
Config {
port: 8080,
host: "0.0.0.0".to_string(),
root: "./".to_string(),
cache: false,
cache_ttl: 0,
threads: 2,
log_file: None,
index: "index.html".to_string(),
proxy_rules,
}
}
}
Loading