Compare commits
10 Commits
e006b8dd12
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 645f53d13e | |||
| 391c9098a6 | |||
| 4866808101 | |||
| 0723c2411d | |||
| db210a2c4e | |||
| 73a2748806 | |||
| 3160febb58 | |||
| 170282cace | |||
| 2225fa870e | |||
| 6b028f822c |
53
README.md
Normal file
53
README.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Plain HTTP
|
||||
|
||||
This is a simple Rust HTTP library. It provides an `HttpApp` struct that acts as the main structure of the application.
|
||||
|
||||
### Example usage
|
||||
|
||||
```rust
|
||||
use std::collections::HashMap;
|
||||
|
||||
use plain_http::*;
|
||||
|
||||
fn get_main(_request: HttpRequest) -> HttpAppRouteResponse {
|
||||
if let Some(body) = _request.body {
|
||||
println!("Body {}", body);
|
||||
}
|
||||
|
||||
HttpAppRouteResponse {
|
||||
body: "hello".to_string(),
|
||||
content_type: "text",
|
||||
status: 200,
|
||||
headers: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_test(_request: HttpRequest) -> HttpAppRouteResponse {
|
||||
HttpAppRouteResponse::from_url("./src/assets/index.html")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
let mut app = HttpApp {
|
||||
config: HttpAppConfig {
|
||||
port: 3000,
|
||||
max_request_size_bytes: 10000,
|
||||
..Default::default()
|
||||
},
|
||||
routes: vec![],
|
||||
default_headers: HashMap::new(),
|
||||
};
|
||||
|
||||
app.add_route(HttpAppRoute {
|
||||
route: "/".to_string(),
|
||||
action: Box::new(get_main),
|
||||
});
|
||||
|
||||
app.add_route(HttpAppRoute {
|
||||
route: "/test".to_string(),
|
||||
action: Box::new(get_test),
|
||||
});
|
||||
|
||||
app.start();
|
||||
}
|
||||
```
|
||||
26
docs/TODO.md
26
docs/TODO.md
@@ -1,14 +1,34 @@
|
||||
# TODO
|
||||
- [ ] Manage requests
|
||||
- [ ] Router
|
||||
|
||||
## V0
|
||||
|
||||
- [x] Manage requests
|
||||
- [NOT NEEDED] HTTP code mapper : not needed because a code will be directly translated on the client (browser / postman)
|
||||
|
||||
## V1
|
||||
|
||||
- [x] Router
|
||||
- [ ] JS / CSS data
|
||||
- [ ] Media manager
|
||||
- [ ] Automatically expose files when a new type of route (page) is defined
|
||||
- [ ] Let the HTML to be the one that links its requirements (js and css)
|
||||
- [ ] Media manager utility
|
||||
- [ ] Let programmer set the default not found response
|
||||
- [x] Manage basic defaults app and route headers
|
||||
- [x] Manage route specific headers
|
||||
|
||||
## V2
|
||||
|
||||
- [ ] Study how to organize functions on structs or types if possible
|
||||
- [ ] Allow middleware
|
||||
- [ ] Middleware to redirect or block petitions
|
||||
- [ ] Middleware to insert headers and data
|
||||
- [ ] Log system
|
||||
- [ ] File management
|
||||
- [ ] Auto-cleanup
|
||||
- [ ] Transversal utility
|
||||
|
||||
***
|
||||
|
||||
## Improvements
|
||||
|
||||
- [ ] Check any potential error in Request Parsing and return a 500 error.
|
||||
|
||||
@@ -1,3 +1,29 @@
|
||||
use super::*;
|
||||
|
||||
// pub fn generate_response(petition: HttpRequest) -> ProcessedResponse {}
|
||||
|
||||
fn format_response_headers(headers: Headers, content_len: usize, content_type: &str) -> String {
|
||||
let mut response_headers = String::new();
|
||||
response_headers.push_str(format!("Content-Length: {}", content_len).as_str());
|
||||
response_headers.push_str(format!("\nContent-Type: {}", content_type).as_str());
|
||||
for (key, value) in headers {
|
||||
response_headers.push_str(format!("\n{key}: {value}").as_str());
|
||||
}
|
||||
return response_headers;
|
||||
}
|
||||
|
||||
pub fn format_response(raw_response: HttpAppRouteResponse) -> ProcessedResponse {
|
||||
ProcessedResponse {
|
||||
data: format!(
|
||||
"HTTP/1.1 {}\r\n{}\r\n\r\n{}",
|
||||
raw_response.status,
|
||||
format_response_headers(
|
||||
raw_response.headers,
|
||||
raw_response.body.len(),
|
||||
raw_response.content_type
|
||||
),
|
||||
raw_response.body
|
||||
),
|
||||
status: raw_response.status,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,9 @@
|
||||
use std::fs;
|
||||
use std::io::prelude::*;
|
||||
use std::net::TcpStream;
|
||||
|
||||
mod generators;
|
||||
mod parsers;
|
||||
mod server;
|
||||
mod types;
|
||||
|
||||
use generators::*;
|
||||
use parsers::*;
|
||||
use types::*;
|
||||
|
||||
pub fn process_petition(stream: &mut TcpStream) -> ProcessedResponse {
|
||||
let mut buffer = [0; 1024]; // TODO: manage this size
|
||||
let _amount = stream.read(&mut buffer);
|
||||
let petition = String::from_utf8_lossy(&buffer[..]);
|
||||
let petition = parse_request(&petition);
|
||||
|
||||
match petition {
|
||||
Ok(petition_parsed) => {
|
||||
let response_status = "200 OK";
|
||||
|
||||
let response_content = fs::read_to_string("./routes/index.html").unwrap();
|
||||
|
||||
let response: ProcessedResponse = ProcessedResponse {
|
||||
data: format!(
|
||||
"HTTP/1.1 {}\r\nContent-Length: {}\r\n\r\n{}",
|
||||
response_status,
|
||||
response_content.len(),
|
||||
response_content
|
||||
),
|
||||
status: 200,
|
||||
};
|
||||
|
||||
response
|
||||
}
|
||||
Err(error) => {
|
||||
let response: ProcessedResponse = ProcessedResponse {
|
||||
data: format!("HTTP/1.1 {}\r\nContent-Length: 0\r\n\r\n", error),
|
||||
status: error,
|
||||
};
|
||||
|
||||
response
|
||||
}
|
||||
}
|
||||
}
|
||||
pub use server::*;
|
||||
pub use types::*;
|
||||
|
||||
124
src/http/server.rs
Normal file
124
src/http/server.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
use std::collections::HashMap;
|
||||
use std::io::prelude::*;
|
||||
use std::net::{SocketAddr, TcpListener, TcpStream};
|
||||
use std::{fs, thread};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl Default for HttpAppConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
port: 3000,
|
||||
max_request_size_bytes: 5120,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for HttpApp<'_> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
config: Default::default(),
|
||||
routes: vec![],
|
||||
default_headers: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpAppRouteResponse<'_> {
|
||||
pub fn from_url(url: &str) -> Self {
|
||||
let response_body = fs::read_to_string(url).unwrap();
|
||||
Self {
|
||||
body: response_body,
|
||||
content_type: "text/html; charset=utf-8",
|
||||
status: 200,
|
||||
headers: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpApp<'_> {
|
||||
fn get_route(&self, path: &str) -> Option<&HttpAppRoute> {
|
||||
// self.routes.first() // TODO: search the real one
|
||||
self.routes.iter().find(|&route| route.route.eq(path))
|
||||
}
|
||||
|
||||
pub fn add_route(&mut self, route: HttpAppRoute) {
|
||||
// TODO: check if already exists
|
||||
self.routes.push(route);
|
||||
}
|
||||
|
||||
pub fn process_petition(&self, stream: &mut TcpStream) -> ProcessedResponse {
|
||||
let mut petition = String::new();
|
||||
const BUFFER_SIZE: usize = 1024;
|
||||
|
||||
loop {
|
||||
let mut buffer = [0; BUFFER_SIZE];
|
||||
let amount = stream.read(&mut buffer);
|
||||
match amount {
|
||||
Ok(size_read) => {
|
||||
if size_read < 1 {
|
||||
break;
|
||||
}
|
||||
|
||||
let buffer_string = String::from_utf8_lossy(&buffer[..]);
|
||||
petition.push_str(&buffer_string);
|
||||
|
||||
if size_read < BUFFER_SIZE {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(_e) => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// FIXME: this does not cover marginal cases: bigger buffer than max_buffer
|
||||
if self.config.max_request_size_bytes > 0
|
||||
&& petition.bytes().len() > self.config.max_request_size_bytes
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let petition = parse_request(&petition);
|
||||
|
||||
match petition {
|
||||
Ok(petition_parsed) => {
|
||||
if let Some(route) = self.get_route(petition_parsed.request.query.path) {
|
||||
let matched_route = (route.action)(petition_parsed);
|
||||
return format_response(matched_route);
|
||||
} else {
|
||||
// TODO: return not found
|
||||
return ProcessedResponse {
|
||||
data: "Error 404".to_string(),
|
||||
status: 400,
|
||||
};
|
||||
}
|
||||
}
|
||||
Err(error) => ProcessedResponse {
|
||||
data: format!("HTTP/1.1 {}\r\nContent-Length: 0\r\n\r\n", error),
|
||||
status: error,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&self) {
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], self.config.port));
|
||||
|
||||
let listener = TcpListener::bind(addr).unwrap();
|
||||
|
||||
println!("Server up and running!");
|
||||
|
||||
for stream in listener.incoming() {
|
||||
let mut _stream = stream.unwrap();
|
||||
|
||||
println!("Connection established!");
|
||||
let response = self.process_petition(&mut _stream);
|
||||
|
||||
thread::spawn(move || {
|
||||
// TODO: manage error case
|
||||
_stream.write(response.data.as_bytes()).unwrap();
|
||||
_stream.flush().unwrap();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,19 +4,28 @@ use std::collections::HashMap;
|
||||
* App types
|
||||
* */
|
||||
pub struct HttpAppConfig {
|
||||
port: u8,
|
||||
pub port: u16,
|
||||
pub max_request_size_bytes: usize, // 0 to not max
|
||||
}
|
||||
|
||||
pub type HttpAppRouteFunction = Box<dyn Fn(HttpRequest) -> String>;
|
||||
pub type HttpAppRouteFunction = Box<fn(HttpRequest) -> HttpAppRouteResponse>;
|
||||
|
||||
pub struct HttpAppRoute<'a> {
|
||||
route: &'a str,
|
||||
action: HttpAppRouteFunction,
|
||||
pub struct HttpAppRoute {
|
||||
pub route: String,
|
||||
pub action: HttpAppRouteFunction,
|
||||
}
|
||||
|
||||
pub struct HttpApp<'a> {
|
||||
config: HttpAppConfig,
|
||||
routes: Vec<HttpAppRoute<'a>>,
|
||||
pub config: HttpAppConfig,
|
||||
pub routes: Vec<HttpAppRoute>,
|
||||
pub default_headers: Headers<'a>,
|
||||
}
|
||||
|
||||
pub struct HttpAppRouteResponse<'a> {
|
||||
pub body: String,
|
||||
pub content_type: &'a str,
|
||||
pub status: Status,
|
||||
pub headers: Headers<'a>,
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
21
src/lib.rs
21
src/lib.rs
@@ -1,25 +1,6 @@
|
||||
use std::io::prelude::*;
|
||||
use std::net::{SocketAddr, TcpListener};
|
||||
|
||||
mod http;
|
||||
|
||||
fn principal() {
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 80));
|
||||
|
||||
let listener = TcpListener::bind(addr).unwrap();
|
||||
|
||||
println!("Server up and running!");
|
||||
|
||||
for stream in listener.incoming() {
|
||||
let mut _stream = stream.unwrap();
|
||||
println!("Connection established!");
|
||||
let response = http::process_petition(&mut _stream);
|
||||
|
||||
// TODO: manage error case
|
||||
let _amount = _stream.write(response.data.as_bytes()).unwrap();
|
||||
_stream.flush().unwrap();
|
||||
}
|
||||
}
|
||||
pub use http::*;
|
||||
|
||||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
|
||||
Reference in New Issue
Block a user