Skip to content

Commit fe25bbf

Browse files
add
0 parents  commit fe25bbf

8 files changed

Lines changed: 353 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/target

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
name = "simple-http"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]

src/http/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod request;
2+
pub mod response;

src/http/request.rs

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
use std::{collections::HashMap, fmt::{write, Display}, hash::Hash, io, result, str::FromStr};
2+
3+
use super::response::HttpResponse;
4+
5+
#[derive(Debug)]
6+
pub struct HttpRequest {
7+
method: Method,
8+
pub resource: Resource,
9+
version: Version,
10+
headers: HttpHeader,
11+
pub request_body: String,
12+
}
13+
14+
impl HttpRequest {
15+
pub fn response(&self) -> io::Result<HttpResponse> {
16+
HttpResponse::new(self)
17+
}
18+
pub fn new(request: &str) -> io::Result<HttpRequest> {
19+
let method: Method = Method::new(request);
20+
let resource: Resource = if let Some(resource) = Resource::new(request) {
21+
resource
22+
} else {
23+
Resource {
24+
path: "".to_string(),
25+
}
26+
};
27+
let version: Version = Version::new(request)
28+
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.msg))?;
29+
30+
let headers: HttpHeader = if let Some(headers) = HttpHeader::new(request) {
31+
headers
32+
} else {
33+
HttpHeader {
34+
headers: HashMap::new(),
35+
}
36+
};
37+
38+
let request_body: String = if let Some((_header, body)) = request.split_once("\r\n\r\n") {
39+
body.to_string()
40+
} else {
41+
String::new()
42+
};
43+
44+
Ok(HttpRequest {
45+
method,
46+
resource,
47+
version,
48+
headers,
49+
request_body,
50+
})
51+
}
52+
}
53+
54+
55+
#[derive(Debug)]
56+
struct HttpHeader {
57+
headers: HashMap<String, String>,
58+
}
59+
60+
impl HttpHeader {
61+
pub fn new(request: &str) -> Option<HttpHeader> {
62+
let mut httpheader = HttpHeader {
63+
headers: HashMap::new(),
64+
};
65+
let (_, header_str) = request.split_once("\r\n")?;
66+
67+
for line in header_str.split_terminator("\r\n") {
68+
if line.is_empty() {
69+
break;
70+
}
71+
let (header, value) = line.split_once(":")?;
72+
httpheader
73+
.headers
74+
.insert(header.trim().to_string(), value.trim().to_string());
75+
}
76+
Some(httpheader)
77+
}
78+
}
79+
80+
#[derive(Debug)]
81+
pub enum Version {
82+
V1_1,
83+
V2_0,
84+
}
85+
86+
impl Display for Version {
87+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88+
let msg = match self {
89+
Version::V1_1 => "HTTP/1.1",
90+
Version::V2_0 => "HTTP/2",
91+
};
92+
write!(f, "{}", msg)
93+
}
94+
}
95+
pub struct VersionError {
96+
msg: String,
97+
}
98+
99+
impl Display for VersionError {
100+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101+
write!(f, "{}", self.msg)
102+
}
103+
}
104+
105+
impl Version {
106+
pub fn new(request: &str) -> Result<Self, VersionError> {
107+
Version::from_str(request)
108+
}
109+
}
110+
impl FromStr for Version {
111+
type Err = VersionError;
112+
113+
fn from_str(request: &str) -> Result<Self, Self::Err> {
114+
let request_split = request.split_once("\r\n");
115+
if let Some((metho_line, _rest)) = request_split {
116+
let splits = metho_line.split_ascii_whitespace();
117+
for split in splits {
118+
if split == "HTTP/1.1" {
119+
return Ok(Version::V1_1);
120+
} else if split == "HTTP/2" || split == "HTTP/2.0" {
121+
return Ok(Version::V2_0);
122+
};
123+
}
124+
};
125+
126+
let invalid = format!("Unknown protocol version in {}", request);
127+
let version_error = VersionError { msg: invalid };
128+
Err(version_error)
129+
}
130+
}
131+
132+
#[derive(Debug)]
133+
enum Method {
134+
Get,
135+
Post,
136+
Uninitialised,
137+
}
138+
139+
impl Method {
140+
pub fn new(request: &str) -> Method {
141+
let request_split = request.split_once("\r\n");
142+
if let Some((method_line, _rest)) = request_split {
143+
let method_line: Option<(&str, &str)> = method_line.split_once(' ');
144+
if let Some((method, _rest)) = method_line {
145+
return match method {
146+
"GET" => Method::Get,
147+
"POST" => Method::Post,
148+
_ => Method::Uninitialised,
149+
};
150+
};
151+
};
152+
Method::Uninitialised
153+
}
154+
pub fn identify(s: &str) -> Method {
155+
match s {
156+
"GET" => Method::Get,
157+
"POST" => Method::Post,
158+
_ => Method::Uninitialised,
159+
}
160+
}
161+
}
162+
163+
#[derive(Debug)]
164+
pub struct Resource {
165+
pub path: String,
166+
}
167+
168+
impl Resource {
169+
pub fn new(request: &str) -> Option<Resource> {
170+
if let Some((request_method, _)) = request.split_once("\r\n") {
171+
let (method, rest) = request_method.split_once(' ')?;
172+
return match Method::identify(method) {
173+
Method::Get | Method::Post => {
174+
let (resource, _protocol_version) = rest.split_once(' ')?;
175+
let resource = resource.trim();
176+
let resource = resource.trim_start_matches('/');
177+
return Some(Resource {
178+
path: resource.to_string(),
179+
});
180+
}
181+
Method::Uninitialised => None,
182+
};
183+
};
184+
None
185+
}
186+
}

src/http/response.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
use std::fmt::Display;
2+
use std::io;
3+
use super:: request::Version;
4+
use super::request::HttpRequest;
5+
6+
7+
#[derive(Debug)]
8+
pub struct HttpResponse{
9+
version: Version,
10+
status:ResponseStatus,
11+
content_length:usize,
12+
accept_ranges:AcceptRanges,
13+
pub response_body:String,
14+
pub current_path:String,
15+
16+
}
17+
impl HttpResponse {
18+
pub fn new(request:&HttpRequest)-> io::Result<HttpResponse>{
19+
let version :Version = Version::V2_0;
20+
let mut status: ResponseStatus = ResponseStatus::NotFound;
21+
let mut content_length : usize=0;
22+
let mut accept_ranges : AcceptRanges = AcceptRanges::None;
23+
let current_path: String = request.resource.path.clone();
24+
let mut response_body = String::new();
25+
26+
let server_root_path = std::env::current_dir()?;
27+
let resource = request.resource.path.clone();
28+
let new_path = server_root_path.join(resource);
29+
if new_path.exists() {
30+
if new_path.is_file(){
31+
let content = std::fs::read_to_string(&new_path)?;
32+
content_length = content.len();
33+
status = ResponseStatus::OK;
34+
accept_ranges = AcceptRanges::Bytes;
35+
let content = format!("{} {}\n{}\ncontent-length: {}\r\n\r\n{}", version, status, accept_ranges, content_length, content);
36+
response_body.push_str(&content);
37+
38+
}
39+
else{
40+
let four_o_four = "<html>
41+
<body>
42+
<h1>404 NOT FOUND</h1>
43+
</body>
44+
</html>";
45+
46+
content_length = four_o_four.len();
47+
48+
let content = format!("{} {}\n{}\ncontent-length: {}\r\n\r\n\
49+
",version,status,accept_ranges,content_length);
50+
response_body.push_str(&content);
51+
}
52+
}
53+
Ok(
54+
HttpResponse{
55+
version,
56+
status,
57+
content_length,
58+
accept_ranges,
59+
response_body,
60+
current_path
61+
}
62+
)
63+
64+
}
65+
}
66+
67+
#[derive(Debug)]
68+
enum ResponseStatus{
69+
OK = 200,
70+
NotFound = 404,
71+
}
72+
73+
impl Display for ResponseStatus{
74+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75+
let msg : &str = match self{
76+
ResponseStatus::OK => "200 OK",
77+
ResponseStatus::NotFound => "404 Not Found",
78+
};
79+
write!(f, "{}", msg)
80+
}
81+
}
82+
83+
#[derive(Debug)]
84+
85+
enum AcceptRanges{
86+
Bytes,
87+
None,
88+
}
89+
90+
impl Display for AcceptRanges {
91+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92+
let msg : &str = match self{
93+
AcceptRanges::Bytes => "accept-ranges:bytes",
94+
AcceptRanges::None => "accept-ranges:none",
95+
};
96+
write!(f, "{}", msg)
97+
}
98+
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod http;

src/main.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use std::{
2+
io::{self, Read, Write},
3+
net::{Ipv4Addr, SocketAddr, TcpListener, TcpStream},
4+
};
5+
6+
use simple_http::http::request;
7+
8+
fn create_socket() -> SocketAddr {
9+
SocketAddr::new(std::net::IpAddr::V4(Ipv4Addr::LOCALHOST), 5500)
10+
}
11+
12+
fn handle_client(stream: &mut TcpStream) -> io::Result<()> {
13+
let mut buffer = [0; 1024];
14+
stream.read(&mut buffer)?;
15+
16+
let buf_str = String::from_utf8_lossy(&buffer);
17+
let request= request::HttpRequest::new(&buf_str)?;
18+
let response = request.response()?;
19+
20+
println!("{:?}", &response);
21+
println!("{}", &response.response_body);
22+
23+
let body = response.response_body.clone();
24+
25+
26+
27+
stream.write(&mut body.as_bytes())?;
28+
stream.flush()?;
29+
Ok(())
30+
}
31+
32+
fn serve(socket: SocketAddr) -> io::Result<()> {
33+
let listner = TcpListener::bind(socket)?;
34+
let mut counter = 0;
35+
for stream in listner.incoming() {
36+
match std::thread::spawn(|| handle_client(&mut stream?)).join() {
37+
Ok(_) => {
38+
counter += 1;
39+
println!("connected stream... {}", counter);
40+
}
41+
Err(_) => continue,
42+
};
43+
}
44+
Ok(())
45+
}
46+
fn main() -> io::Result<()> {
47+
let socket = create_socket();
48+
serve(socket)?;
49+
Ok(())
50+
}

0 commit comments

Comments
 (0)