A simple http server library built from scratch.
Using only the threadpool, urlencoding, num_cpus and flate2. (and ofc the built in std)
Using bumpalo allocator for the body buffer.
Heavily inspired by ExpressJs
cargo add choki
or add it in your Cargo.toml
choki = "1.1.13"
- Create GET and POST endpoints and use them like you are used to in express.js
- Create Static endpoints
use choki::structs::{Request, Response};
use choki::Server;
let mut server: Server<u8> = Server::new(max_content_length: Some(1024),public_var: None);
You can set max request size and a public var that is in this case type u8 and it is cloned to every thread/request.
server.get("/".to_string(), |req: Request, mut res: Response, public_var: Option<u8>| {
res.send_string("HI")
}).unwrap();
server.post("/".to_string(), |req: Request, mut res: Response, public_var: Option<u8>| {
res.send_string("Letter!")
}).unwrap();
server.put("/put".to_string(), |req: Request, mut res: Response, public_var: Option<u8>| {
res.send_string("Hello world!")
}).unwrap();
server.delete("/delete".to_string(), |req: Request, mut res: Response, public_var: Option<u8>| {
res.send_string("Boom!")
}).unwrap();
server.on(
RequestType::Other("Custom".to_string()),
"/",
|req: Request, mut res: Response, public_var: Option<u8>| {
res.send_string("HI custom one")
}
).unwrap();
server.new_static("/images", "./tests/images").unwrap(); // The first one is the path in the browser for example: example.com/images and the second one is the exposed path from the computer(local)
As of 1.0.8
choki supports params
server.post("/search/[id]".to_string(), |req: Request, mut res: Response, public_var: Option<u8>| {
println!("{}", req.params.get("id").unwrap()); // if i make request to /search/pizza this will print pizza
res.send_string("HI")
}).unwrap();
Also queries and body are supported.
req.body
is a Vec<BodyItem>
which are the items in the body (if multipart-form and etc. (you can check it req.content_type
));
req.query
are the queries (/search?name=123 the thing after ?)
Middleware is also supported.
server.use_middleware(|url: &Url, req: &Request, mut res: &Response, public_var: &Option<u8>| {
println!("Ip {}", req.ip.clone().unwrap_or_default());
return true;
});
Custom error logger function
server.use_logger(|input: &HttpServerError| {
println!("Hi custom error printer: {}", input.reason);
});
So they are four simple functions There two types of responses:
- Sending the data in one big chunk
res.send_bytes(&mut self, data: &[u8], content_type: Option<ContentType>) // sends raw bytes with content type you provide (you can provide ContentType::None and let the browser decide)
res.send_string(&mut self, data: &str) // sends string as response
res.send_json(&mut self, data: &str) // sends json as response
- Sending it chunked
res.send_bytes_chunked(&mut self, data: &[u8], content_type: Option<ContentType>)
res.send_string_chunked(&mut self, data: &str)
res.send_json_chunked(&mut self, data: &str)
res.send_code(&mut self, code: usize) // sends a HTTP response code (404,200...)
Also you can send download bytes or streams
res.send_download_bytes(&mut self, data: &[u8], file_name: &str) // Sends bytes and the browser is goind to start to download it.
res.send_download_stream(
&mut self,
stream: BufReader<impl Read>,
file_name: &str,
file_size: Option<&u64>) // Pipes a stream and the browser is goind to start to download it.
And piping stream
res.pipe_stream(
&mut self,
mut stream: BufReader<impl Read>,
content_type: Option<ContentType>,
stream_size: Option<&u64>
)
Sending raw code
res.send_code(&mut self, code: ResponseCode) // sends a HTTP response code (404,200...)
as of 1.0.3
you can set or delete cookies and ofc read them.
pub struct Cookie {
pub name: String,
pub value: String,
pub path: String,
pub expires: String,
}
You can read cookies using req.cookies (stored as a vec)
You can set/delete them using
res.set_cookie(cookie: &Cookie);
res.delete_cookie(name: &str);
as of 1.0.6
you can set or delete headers and ofc read them.
pub struct Header {
pub name: String,
pub value: String,
}
You can set/delete them using
res.set_header(header: &Header);
res.delete_cookie(name: &str);
When you create an endpoint you have Request and Response.
The request holds info about the request.
pub struct Request {
pub query: HashMap<String, String>, // for example in the url www.example.com/?name=Kartof the query will be ["name" => "Kartof"] as hashmap
pub params: HashMap<String, String>, // a hashmap containing every param with name and value
pub headers: Vec<Header>,
pub cookies: Vec<Cookie>,
// User data
pub ip: Option<String>,
pub user_agent: Option<String>,
pub content_encoding: Option<Vec<Encoding>>, // The compression it can accept (zlib...)
pub content_length: usize,
// BODY
pub content_type: Option<ContentType>, // The content type of the body
}
To get the body use the function body()
let body: Vec<BodyItem<'_>> = req.body();
You need to make the server actually 'listen' for requests so use this method:
server.listen(3000, None, None, || { println!("Server is listening on port 3000") }).unwrap();
And finally because you wanna keep the main thread running or else the server will close as soon as the code runs.
Add this at the end of your file
Server::<i32>::lock();
Also in the src folder there is a main.rs
file which can be used as an example.
Around 30k request per second with i5-9400f. It is very light on ram. About 300kb.