Finialized final implenentation
This commit is contained in:
parent
4847beca3f
commit
9a4556e881
10
.idea/.gitignore
vendored
10
.idea/.gitignore
vendored
@ -1,10 +0,0 @@
|
|||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# Editor-based HTTP Client requests
|
|
||||||
/httpRequests/
|
|
||||||
# Datasource local storage ignored files
|
|
||||||
/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
||||||
# GitHub Copilot persisted chat sessions
|
|
||||||
/copilot/chatSessions
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="CheckStyle-IDEA" serialisationVersion="2">
|
|
||||||
<checkstyleVersion>10.14.0</checkstyleVersion>
|
|
||||||
<scanScope>JavaOnly</scanScope>
|
|
||||||
<copyLibs>true</copyLibs>
|
|
||||||
<option name="thirdPartyClasspath" />
|
|
||||||
<option name="activeLocationIds" />
|
|
||||||
<option name="locations">
|
|
||||||
<list>
|
|
||||||
<ConfigurationLocation id="bundled-sun-checks" type="BUNDLED" scope="All" description="Sun Checks">(bundled)</ConfigurationLocation>
|
|
||||||
<ConfigurationLocation id="bundled-google-checks" type="BUNDLED" scope="All" description="Google Checks">(bundled)</ConfigurationLocation>
|
|
||||||
</list>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectModuleManager">
|
|
||||||
<modules>
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/udemy-course-server.iml" filepath="$PROJECT_DIR$/.idea/udemy-course-server.iml" />
|
|
||||||
</modules>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="JAVA_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
|
||||||
<exclude-output />
|
|
||||||
<content url="file://$MODULE_DIR$">
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.idea/copilot/chatSessions" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
93
public/index.html
Normal file
93
public/index.html
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Your Portfolio</title>
|
||||||
|
<style>
|
||||||
|
/* Reset default styles */
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set background image */
|
||||||
|
body {
|
||||||
|
/* background-image: url('https://slink.uzair.page/image/d2185cb4-63e1-4ca9-b14b-85c071863e3f.jpg');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center; */
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
color: #333; /* Dark text color */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header styles */
|
||||||
|
header {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 3rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: #fff; /* Light text color */
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: #ddd; /* Lighter text color */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Portfolio section */
|
||||||
|
.portfolio {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 80vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project {
|
||||||
|
background-color: rgba(255, 255, 255, 0.9); /* Light background */
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin: 1rem;
|
||||||
|
max-width: 400px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project h2 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer styles */
|
||||||
|
footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
color: #ddd; /* Lighter text color */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>Your Name</h1>
|
||||||
|
<p>Web Developer & Designer</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="portfolio">
|
||||||
|
<div class="project">
|
||||||
|
<h2>Project 1</h2>
|
||||||
|
<p>Description of your project goes here.</p>
|
||||||
|
</div>
|
||||||
|
<div class="project">
|
||||||
|
<h2>Project 2</h2>
|
||||||
|
<p>Description of another project.</p>
|
||||||
|
</div>
|
||||||
|
<!-- Add more project divs as needed -->
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>Contact: your@email.com</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
24
public/login.html
Normal file
24
public/login.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Login</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Login</h1>
|
||||||
|
<form>
|
||||||
|
<label for="username">Username:</label>
|
||||||
|
<input type="text" id="username" name="username" required>
|
||||||
|
<br>
|
||||||
|
<label for="password">Password:</label>
|
||||||
|
<input type="password" id="password" name="password" required>
|
||||||
|
<br>
|
||||||
|
<button type="submit">Login</button>
|
||||||
|
</form>
|
||||||
|
<p>Don't have an account? <a href="signup.html">Sign up</a></p>
|
||||||
|
<p>Forgot password? <a href="forgotpassword.html">Reset password</a></p>
|
||||||
|
<button onclick="window.location.href='index.html'">Go to Index</button>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
6
public/script.js
Normal file
6
public/script.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
const button = document.querySelector('button');
|
||||||
|
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
// Add functionality to the button click here (e.g., alert, change color)
|
||||||
|
alert('Button Clicked!');
|
||||||
|
});
|
||||||
61
public/style.css
Normal file
61
public/style.css
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
background: linear-gradient(to right, #f2f2f2, #eee);
|
||||||
|
animation: gradient 10s ease-in-out infinite alternate;
|
||||||
|
filter: blur(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gradient {
|
||||||
|
0% {
|
||||||
|
background: linear-gradient(to right, #f2f2f2, #eee);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background: linear-gradient(to right, #eee, #f7f7f7);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background: linear-gradient(to right, #f7f7f7, #f2f2f2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
position: relative;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 40px;
|
||||||
|
text-align: center;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 3em;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 1.2em;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 15px 32px;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 4px 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,6 +1,10 @@
|
|||||||
|
use crate::http::ParseError;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum Method {
|
pub enum Method {
|
||||||
GET(String),
|
GET,
|
||||||
DELETE(u64),
|
DELETE,
|
||||||
POST,
|
POST,
|
||||||
PUT,
|
PUT,
|
||||||
HEAD,
|
HEAD,
|
||||||
@ -8,4 +12,25 @@ pub enum Method {
|
|||||||
OPTIONS,
|
OPTIONS,
|
||||||
TRACE,
|
TRACE,
|
||||||
PATCH,
|
PATCH,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromStr for Method {
|
||||||
|
type Err = MethodError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"GET" => Ok(Self::GET),
|
||||||
|
"DELETE" => Ok(Self::DELETE),
|
||||||
|
"POST" => Ok(Self::POST),
|
||||||
|
"PUT" => Ok(Self::PUT),
|
||||||
|
"HEAD" => Ok(Self::HEAD),
|
||||||
|
"CONNECT" => Ok(Self::CONNECT),
|
||||||
|
"OPTIONS" => Ok(Self::OPTIONS),
|
||||||
|
"TRACE" => Ok(Self::TRACE),
|
||||||
|
"PATCH" => Ok(Self::PATCH),
|
||||||
|
_ => Err(MethodError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MethodError;
|
||||||
@ -1,6 +1,12 @@
|
|||||||
pub use request::Request;
|
pub use request::Request;
|
||||||
pub use method::Method;
|
pub use method::Method;
|
||||||
pub use request::ParseError;
|
pub use request::ParseError;
|
||||||
|
pub use query_string::{QueryString, Value as QueryStringValue};
|
||||||
|
pub use response::Response;
|
||||||
|
pub use status_code::StatusCode;
|
||||||
|
|
||||||
pub mod request;
|
pub mod request;
|
||||||
pub mod method;
|
pub mod method;
|
||||||
|
pub mod query_string;
|
||||||
|
pub mod response;
|
||||||
|
pub mod status_code;
|
||||||
41
src/http/query_string.rs
Normal file
41
src/http/query_string.rs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
use std::{collections::HashMap, thread::current};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct QueryString<'buffer_lifetime> {
|
||||||
|
data: HashMap<&'buffer_lifetime str, Value<'buffer_lifetime>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Value<'buffer_lifetime> {
|
||||||
|
Single(&'buffer_lifetime str),
|
||||||
|
Multiple(Vec<&'buffer_lifetime str>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'buffer_lifetime> QueryString<'buffer_lifetime> {
|
||||||
|
pub fn get(&self, key: &str) -> Option<&Value> {
|
||||||
|
self.data.get(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'buffer_lifetime> From<&'buffer_lifetime str> for QueryString<'buffer_lifetime> {
|
||||||
|
fn from(value: &'buffer_lifetime str) -> Self {
|
||||||
|
let mut data = HashMap::new();
|
||||||
|
|
||||||
|
for sub_str in value.split('&') {
|
||||||
|
let mut key = sub_str;
|
||||||
|
let mut value = "";
|
||||||
|
if let Some(i) = sub_str.find('=') {
|
||||||
|
key = &sub_str[..i];
|
||||||
|
value = &sub_str[i + 1 ..]
|
||||||
|
}
|
||||||
|
data.entry(key).and_modify(|existing: &mut Value| match existing {
|
||||||
|
Value::Single(previous_value) => {
|
||||||
|
*existing = Value::Multiple(vec![previous_value, value]);
|
||||||
|
},
|
||||||
|
Value::Multiple(vec) => vec.push(value),
|
||||||
|
}).or_insert(Value::Single(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryString { data }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,35 +1,81 @@
|
|||||||
use std::str;
|
use super::method::{Method, MethodError};
|
||||||
use super::method::Method;
|
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::io::Error;
|
use std::fmt::{Debug, Display, Formatter};
|
||||||
use std::fmt::{Display, Debug, Formatter};
|
use std::str;
|
||||||
use std::str::Utf8Error;
|
use std::str::Utf8Error;
|
||||||
|
use crate::http::query_string::QueryString;
|
||||||
|
|
||||||
pub struct Request {
|
#[derive(Debug)]
|
||||||
path: String,
|
pub struct Request<'buffer_lifetime> {
|
||||||
query_string: Option<String>,
|
path: &'buffer_lifetime str,
|
||||||
|
query_string: Option<QueryString<'buffer_lifetime>>,
|
||||||
method: Method,
|
method: Method,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Request {
|
impl<'buffer_lifetime> Request<'buffer_lifetime> {
|
||||||
|
pub fn path(&self) -> &str {
|
||||||
|
self.path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn query_string(&self) -> Option<&QueryString<'buffer_lifetime>> {
|
||||||
|
self.query_string.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn method(&self) -> &Method {
|
||||||
|
&self.method
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&[u8]> for Request {
|
impl<'buffer_lifetime> TryFrom<&'buffer_lifetime [u8]> for Request<'buffer_lifetime> {
|
||||||
type Error = ParseError;
|
type Error = ParseError;
|
||||||
|
|
||||||
//GET /search?name=abc&sort=1 HTTP/1.1
|
//GET /search?name=abc&sort=1 HTTP/1.1\r\n...HEADERS...
|
||||||
fn try_from(buffer: &[u8]) -> Result<Self, Self::Error> {
|
fn try_from(buffer: &'buffer_lifetime [u8]) -> Result<Request<'buffer_lifetime>, Self::Error> {
|
||||||
match str::from_utf8(buffer){
|
let request = str::from_utf8(buffer)?;
|
||||||
Ok(request) => {}
|
|
||||||
Err(_) => return Err(ParseError::InvalidEncoding),
|
let (method, request) = get_next_word(request).ok_or(ParseError::InvalidRequest)?;
|
||||||
}
|
let (mut path, request) = get_next_word(request).ok_or(ParseError::InvalidRequest)?;
|
||||||
match str::from_utf8(buffer).or(Err(ParseError::InvalidEncoding)) {
|
let (protocol, _) = get_next_word(request).ok_or(ParseError::InvalidRequest)?;
|
||||||
Ok(request) => {}
|
|
||||||
Err(e) => return Err(e)
|
if protocol != "HTTP/1.1" {
|
||||||
|
return Err(ParseError::InvalidProtocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
let request = str::from_utf8(buffer).or(Err(ParseError::InvalidEncoding))?;
|
let method: Method = method.parse()?;
|
||||||
|
|
||||||
|
let mut query_string = None;
|
||||||
|
|
||||||
|
if let Some(i) = path.find('?') {
|
||||||
|
query_string = Some(QueryString::from(&path[i + 1..]));
|
||||||
|
path = &path[..i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(Self {
|
||||||
|
path,
|
||||||
|
query_string,
|
||||||
|
method,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_next_word(request: &str) -> Option<(&str, &str)> {
|
||||||
|
for (index, request_character) in request.chars().enumerate() {
|
||||||
|
if request_character == ' ' || request_character == '\r' {
|
||||||
|
return Some((&request[..index], &request[index + 1..]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<MethodError> for ParseError {
|
||||||
|
fn from(value: MethodError) -> Self {
|
||||||
|
self::ParseError::InvalidMethod
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Utf8Error> for ParseError {
|
||||||
|
fn from(_: Utf8Error) -> Self {
|
||||||
|
Self::InvalidEncoding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,20 +95,16 @@ pub enum ParseError {
|
|||||||
InvalidRequest,
|
InvalidRequest,
|
||||||
InvalidEncoding,
|
InvalidEncoding,
|
||||||
InvalidProtocol,
|
InvalidProtocol,
|
||||||
InvalidMethod
|
InvalidMethod,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParseError{
|
impl ParseError {
|
||||||
fn message(&self) -> &str{
|
fn message(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
ParseError::InvalidRequest => {"Invalid Request"}
|
ParseError::InvalidRequest => "Invalid Request",
|
||||||
ParseError::InvalidEncoding => {"Invalid Encoding"}
|
ParseError::InvalidEncoding => "Invalid Encoding",
|
||||||
ParseError::InvalidProtocol => {"Invalid Protocol"}
|
ParseError::InvalidProtocol => "Invalid Protocol",
|
||||||
ParseError::InvalidMethod => {"Invalid Method"}
|
ParseError::InvalidMethod => "Invalid Method",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error for ParseError {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
31
src/http/response.rs
Normal file
31
src/http/response.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use std::{fmt::Display, net::TcpStream};
|
||||||
|
use std::io::{Write, Result as IOResult};
|
||||||
|
|
||||||
|
use super::status_code::StatusCode;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Response {
|
||||||
|
status_code: StatusCode,
|
||||||
|
body: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Response {
|
||||||
|
pub fn new(status_code: StatusCode, body: Option<String>) -> Self {
|
||||||
|
Response { status_code, body }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send(&self, stream: &mut impl Write) -> IOResult<()>{
|
||||||
|
let body = match &self.body {
|
||||||
|
Some(body) => body,
|
||||||
|
None => "",
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(
|
||||||
|
stream,
|
||||||
|
"HTTP/1.1 {} {}\r\n\r\n{}",
|
||||||
|
self.status_code,
|
||||||
|
self.status_code.reason_phrase(),
|
||||||
|
body
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/http/status_code.rs
Normal file
24
src/http/status_code.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
use std::fmt::{Display, Formatter, Result as FmtResult};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum StatusCode {
|
||||||
|
Ok = 200,
|
||||||
|
BadRequest = 400,
|
||||||
|
NotFound = 404
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatusCode {
|
||||||
|
pub fn reason_phrase(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Ok => "Ok",
|
||||||
|
Self::BadRequest => "Bad Request",
|
||||||
|
Self::NotFound => "Not Found",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for StatusCode {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||||
|
write!(f, "{}", *self as u16)
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/main.rs
13
src/main.rs
@ -1,11 +1,20 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
#![allow(unused_imports)]
|
||||||
|
|
||||||
mod server;
|
mod server;
|
||||||
mod http;
|
mod http;
|
||||||
|
mod website_handler;
|
||||||
|
|
||||||
use server::Server;
|
use server::Server;
|
||||||
use http::Request;
|
use http::Request;
|
||||||
use http::Method;
|
use website_handler::WebsiteHandler;
|
||||||
|
use std::env;
|
||||||
|
//use http::Method;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
let default_path = format!("{}\\public", env!("CARGO_MANIFEST_DIR").replace("/", "\\"));
|
||||||
|
let public_path = env::var("PUBLIC_PATH").unwrap_or(default_path);
|
||||||
|
println!("public path is {}", public_path);
|
||||||
let server = Server::new(String::from("127.0.0.1:3000"));
|
let server = Server::new(String::from("127.0.0.1:3000"));
|
||||||
server.run();
|
server.run(WebsiteHandler::new(public_path));
|
||||||
}
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
GET localhost:3000
|
||||||
@ -1,11 +1,21 @@
|
|||||||
use std::io::Read;
|
use crate::http::{request, ParseError, Request, Response, StatusCode};
|
||||||
|
use std::io::{Read, Write};
|
||||||
use std::net::TcpListener;
|
use std::net::TcpListener;
|
||||||
use std::process;
|
use std::process;
|
||||||
use std::ptr::read;
|
use std::ptr::read;
|
||||||
use super::Request;
|
|
||||||
|
// use super::Request;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
pub trait Handler {
|
||||||
|
fn handle_request(&mut self, request: &Request) -> Response;
|
||||||
|
fn handle_bad_request(&mut self, error: &ParseError) -> Response {
|
||||||
|
println!("Failed to parse request: {}", error);
|
||||||
|
Response::new(StatusCode::BadRequest, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A sample HTTP request looks like this:
|
/// A sample HTTP request looks like this:
|
||||||
/// ```
|
/// ```
|
||||||
/// GET / HTTP/1.1\r\n
|
/// GET / HTTP/1.1\r\n
|
||||||
@ -18,12 +28,10 @@ pub struct Server {
|
|||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
pub fn new(address: String) -> Self {
|
pub fn new(address: String) -> Self {
|
||||||
Self {
|
Self { address }
|
||||||
address,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(self) {
|
pub fn run(self, mut handler: impl Handler) {
|
||||||
let (ip, port) = self.address.split_at(self.address.find(":").unwrap());
|
let (ip, port) = self.address.split_at(self.address.find(":").unwrap());
|
||||||
let port = &port[1..];
|
let port = &port[1..];
|
||||||
|
|
||||||
@ -44,11 +52,15 @@ impl Server {
|
|||||||
Ok(read_bytes) => {
|
Ok(read_bytes) => {
|
||||||
println!("Read {} bytes", read_bytes);
|
println!("Read {} bytes", read_bytes);
|
||||||
println!("Received a request: {}", String::from_utf8_lossy(&buffer));
|
println!("Received a request: {}", String::from_utf8_lossy(&buffer));
|
||||||
match Request::try_from(&buffer[..]){
|
|
||||||
Ok(request) => {},
|
let response = match Request::try_from(&buffer[..]) {
|
||||||
Err(e) => println!("Failed to parse a request: {e}")
|
Ok(request) => handler.handle_request(&request),
|
||||||
|
Err(e) => handler.handle_bad_request(&e),
|
||||||
};
|
};
|
||||||
let result: &Result<Request, _> = &buffer[..].try_into();
|
|
||||||
|
if let Err(e) = response.send(&mut stream) {
|
||||||
|
println!("Failed to send response: {}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
println!("Failed to read: {}", error);
|
println!("Failed to read: {}", error);
|
||||||
@ -62,4 +74,4 @@ impl Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
73
src/website_handler.rs
Normal file
73
src/website_handler.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
use std::{fs, path};
|
||||||
|
|
||||||
|
use crate::http::{Method, Request, Response, StatusCode};
|
||||||
|
|
||||||
|
use super::server::Handler;
|
||||||
|
|
||||||
|
pub struct WebsiteHandler {
|
||||||
|
public_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WebsiteHandler {
|
||||||
|
pub fn new(public_path: String) -> Self {
|
||||||
|
Self { public_path }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_file(&self, file_path: &str) -> Option<String> {
|
||||||
|
let path = format!("{}\\{}", self.public_path, file_path);
|
||||||
|
|
||||||
|
match fs::canonicalize(path) {
|
||||||
|
Ok(path) => {
|
||||||
|
if path.starts_with(&self.public_path) {
|
||||||
|
fs::read_to_string(path).ok()
|
||||||
|
} else {
|
||||||
|
println!("Directory Traversal Attack Attempted: {}", file_path);
|
||||||
|
Some("".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler for WebsiteHandler {
|
||||||
|
fn handle_request(&mut self, request: &Request) -> Response {
|
||||||
|
match request.method() {
|
||||||
|
Method::GET => match request.path() {
|
||||||
|
"/" => Response::new(StatusCode::Ok, self.read_file("index.html")),
|
||||||
|
"/login" => Response::new(StatusCode::Ok, self.read_file("login.html")),
|
||||||
|
"/signup" => Response::new(
|
||||||
|
StatusCode::Ok,
|
||||||
|
Some(
|
||||||
|
r#"
|
||||||
|
<h1>Sign Up</h1>
|
||||||
|
<form>
|
||||||
|
<label for="username">Username:</label>
|
||||||
|
<input type="text" id="username" name="username"><br><br>
|
||||||
|
<label for="email">Email:</label>
|
||||||
|
<input type="email" id="email" name="email"><br><br>
|
||||||
|
<label for="password">Password:</label>
|
||||||
|
<input type="password" id="password" name="password"><br><br>
|
||||||
|
<input type="submit" value="Sign Up">
|
||||||
|
</form>
|
||||||
|
"#
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
path => match self.read_file(path) {
|
||||||
|
Some(contents) => Response::new(StatusCode::Ok, Some(contents)),
|
||||||
|
None => Response::new(StatusCode::NotFound, None),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Method::DELETE => todo!(),
|
||||||
|
Method::POST => todo!(),
|
||||||
|
Method::PUT => todo!(),
|
||||||
|
Method::HEAD => todo!(),
|
||||||
|
Method::CONNECT => todo!(),
|
||||||
|
Method::OPTIONS => todo!(),
|
||||||
|
Method::TRACE => todo!(),
|
||||||
|
Method::PATCH => todo!(),
|
||||||
|
_ => Response::new(StatusCode::NotFound, None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user