pushing near-usable frontend progress before attempting full refactor
This commit is contained in:
parent
caa6f98caa
commit
2229656a20
19
src/gem_fe.rs
Normal file
19
src/gem_fe.rs
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2025 silt <silt@tebibyte.media>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*
|
||||
* This file is part of Mintee.
|
||||
*
|
||||
* Mintee is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* Mintee is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Mintee. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
157
src/http_fe.rs
157
src/http_fe.rs
@ -20,12 +20,7 @@
|
||||
|
||||
use httparse;
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt,
|
||||
io::{self, BufRead, BufReader, Read},
|
||||
net::{Incoming, SocketAddr, TcpListener, TcpStream, ToSocketAddrs},
|
||||
process::exit,
|
||||
time::Duration,
|
||||
error::Error, fmt, io::{self, BufRead, BufReader, Read}, net::{Incoming, SocketAddr, TcpListener, TcpStream, ToSocketAddrs}, ops::Deref, path::{Component, PathBuf}, process::exit, str::FromStr, time::Duration
|
||||
};
|
||||
|
||||
use crate::{eyap, yap};
|
||||
@ -33,7 +28,48 @@ use crate::{eyap, yap};
|
||||
pub use super::server::{Frontend, FrontendImpl};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum HttpErrorKind {
|
||||
#[non_exhaustive]
|
||||
pub enum HttpMethod {
|
||||
GET,
|
||||
POST,
|
||||
HEAD,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for HttpMethod {
|
||||
type Error = HttpError;
|
||||
fn try_from(val: &str) -> Result<Self, Self::Error> {
|
||||
use HttpMethod::*;
|
||||
match val {
|
||||
"GET" => Ok(GET),
|
||||
"POST" => Ok(POST),
|
||||
"HEAD" => Ok(HEAD),
|
||||
_ => Err(HttpError {
|
||||
kind: ResponseCode::MethodNotAllowed,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HttpMethod> for &str {
|
||||
fn from(val: HttpMethod) -> Self {
|
||||
use HttpMethod::*;
|
||||
match val {
|
||||
GET => "GET",
|
||||
POST => "POST",
|
||||
HEAD => "HEAD",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[non_exhaustive]
|
||||
pub enum ResponseCode {
|
||||
Okay,
|
||||
Created,
|
||||
MovedPermanently,
|
||||
SeeOther,
|
||||
TemporaryRedirect,
|
||||
PermanentRedirect,
|
||||
BadRequest,
|
||||
Unauthorized,
|
||||
Forbidden,
|
||||
@ -46,10 +82,16 @@ pub enum HttpErrorKind {
|
||||
HttpVersionNotSupported,
|
||||
}
|
||||
|
||||
impl From<HttpErrorKind> for usize {
|
||||
fn from(val: HttpErrorKind) -> Self {
|
||||
use HttpErrorKind::*;
|
||||
impl From<ResponseCode> for usize {
|
||||
fn from(val: ResponseCode) -> Self {
|
||||
use ResponseCode::*;
|
||||
match val {
|
||||
Okay => 200,
|
||||
Created => 201,
|
||||
MovedPermanently => 301,
|
||||
SeeOther => 303,
|
||||
TemporaryRedirect => 307,
|
||||
PermanentRedirect => 308,
|
||||
BadRequest => 400,
|
||||
Unauthorized => 401,
|
||||
Forbidden => 403,
|
||||
@ -64,10 +106,16 @@ impl From<HttpErrorKind> for usize {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HttpErrorKind> for &str {
|
||||
fn from(val: HttpErrorKind) -> Self {
|
||||
use HttpErrorKind::*;
|
||||
impl From<ResponseCode> for &str {
|
||||
fn from(val: ResponseCode) -> Self {
|
||||
use ResponseCode::*;
|
||||
match val {
|
||||
Okay => "OK",
|
||||
Created => "Created",
|
||||
MovedPermanently => "Moved Permanently",
|
||||
SeeOther => "See Other",
|
||||
TemporaryRedirect => "Temporary Redirect",
|
||||
PermanentRedirect => "Permanent Redirect",
|
||||
BadRequest => "Bad Request",
|
||||
Unauthorized => "Unauthorized",
|
||||
Forbidden => "Forbidden",
|
||||
@ -84,7 +132,13 @@ impl From<HttpErrorKind> for &str {
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct HttpError {
|
||||
kind: HttpErrorKind,
|
||||
kind: ResponseCode,
|
||||
}
|
||||
|
||||
impl HttpError {
|
||||
pub fn new(kind: ResponseCode) -> Self {
|
||||
Self { kind }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for HttpError {
|
||||
@ -110,7 +164,7 @@ impl From<httparse::Error> for HttpError {
|
||||
fn from(value: httparse::Error) -> Self {
|
||||
HttpError {
|
||||
kind: match value {
|
||||
_ => HttpErrorKind::BadRequest,
|
||||
_ => ResponseCode::BadRequest,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -141,7 +195,39 @@ pub struct FeStorage {
|
||||
// TODO: tera template store
|
||||
}
|
||||
|
||||
impl Frontend<FeStorage, FeConfig> {}
|
||||
impl Frontend<FeStorage, FeConfig> {
|
||||
fn router(
|
||||
method: HttpMethod,
|
||||
path: PathBuf,
|
||||
params: Option<Vec<(&str, &str)>>,
|
||||
headers: &[httparse::Header],
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
use HttpMethod::*;
|
||||
// unwrapping is safe here because the resource path it came from is a valid UTF-8 &str
|
||||
match (method, path.components().map(|c| c.as_os_str().to_str().unwrap()).collect::<Vec<&str>>().deref(), params, headers) {
|
||||
(GET, ["/", "index.html"], _, _) => {
|
||||
todo!()
|
||||
}
|
||||
(GET, ["/", "login.html"], _, _) => {
|
||||
todo!()
|
||||
}
|
||||
// can be cleaned up with rust/rust-lang #76001 or maybe #87599
|
||||
(GET, ["/", user], _, _) if user.starts_with('~') => {
|
||||
todo!()
|
||||
}
|
||||
(GET, ["/", user, repo], _, _) if user.starts_with('~') => {
|
||||
todo!()
|
||||
}
|
||||
(GET, ["/", project], _, _) if project.starts_with('+') => {
|
||||
todo!()
|
||||
}
|
||||
(GET, ["/", project, repo], _, _) if project.starts_with('+') => {
|
||||
todo!()
|
||||
}
|
||||
_ => Err(Box::new(HttpError::new(ResponseCode::NotFound))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Frontend<FeStorage, FeConfig> {
|
||||
type Item = Incoming<'static>;
|
||||
@ -178,6 +264,7 @@ impl FrontendImpl for Frontend<FeStorage, FeConfig> {
|
||||
let mut req = httparse::Request::new(&mut headers);
|
||||
let buf: &mut Vec<u8> = &mut vec![];
|
||||
|
||||
// TODO: parse the rest of the request before sending to the router
|
||||
stream_read.take(8192).read_until(b'\n', buf)?;
|
||||
let res = req.parse(buf);
|
||||
|
||||
@ -189,15 +276,43 @@ impl FrontendImpl for Frontend<FeStorage, FeConfig> {
|
||||
method: Some(method),
|
||||
path: Some(path),
|
||||
version: Some(1),
|
||||
headers: _,
|
||||
headers,
|
||||
},
|
||||
) => todo!(),
|
||||
) => {
|
||||
// separate path containing get params into path and kv vec
|
||||
let (path, params) = path.split_once("?").map_or_else(
|
||||
|| (PathBuf::from_str(path).unwrap(), None),
|
||||
|(path, args)| {
|
||||
(
|
||||
PathBuf::from_str(path).unwrap(),
|
||||
Some(
|
||||
args.split('&')
|
||||
.filter_map(|e| e.split_once('='))
|
||||
.collect::<Vec<(&str, &str)>>(),
|
||||
),
|
||||
)
|
||||
},
|
||||
);
|
||||
if path.is_absolute() {
|
||||
// context-valid lexical normalization without da feature
|
||||
let path = PathBuf::from_iter(path.components().try_fold(Vec::<Component>::new(), |mut acc, item| -> Result<Vec<Component<'_>>, Box<dyn Error>> {
|
||||
match item {
|
||||
Component::CurDir => Ok(acc),
|
||||
Component::RootDir => {acc.push(item); Ok(acc)},
|
||||
Component::Normal(_) => {acc.push(item); Ok(acc)},
|
||||
Component::ParentDir => {acc.pop_if(|c| c != &Component::RootDir); Ok(acc)},
|
||||
Component::Prefix(_) => Err(Box::new(HttpError::new(ResponseCode::BadRequest)) as Box<dyn Error>),
|
||||
}
|
||||
})?);
|
||||
Self::router(method.try_into()?, path, params, headers)
|
||||
} else {
|
||||
Err(Box::new(HttpError::new(ResponseCode::BadRequest)) as Box<dyn Error>)
|
||||
}
|
||||
}
|
||||
|
||||
// Malformed request lines and HTTP/1.1 requests without a Host header
|
||||
(Ok(httparse::Status::Partial), _) | (Ok(httparse::Status::Complete(_)), _) => {
|
||||
Err(Box::new(HttpError {
|
||||
kind: HttpErrorKind::BadRequest,
|
||||
}) as Box<dyn Error>)
|
||||
Err(Box::new(HttpError::new(ResponseCode::BadRequest)) as Box<dyn Error>)
|
||||
}
|
||||
|
||||
// Fatal parsing error; obvious bad request
|
||||
|
@ -21,11 +21,8 @@
|
||||
use std::{
|
||||
array,
|
||||
error::Error,
|
||||
io::{self, BufRead, BufReader, BufWriter, Read, Write},
|
||||
net::{SocketAddr, TcpListener, TcpStream, ToSocketAddrs},
|
||||
num::NonZeroUsize,
|
||||
os::{linux::net::TcpStreamExt, unix::thread::JoinHandleExt},
|
||||
thread::{self, JoinHandle, Thread},
|
||||
io::{Read, Write},
|
||||
thread::{self, JoinHandle},
|
||||
};
|
||||
|
||||
use crate::{eyap, yap};
|
||||
|
Loading…
x
Reference in New Issue
Block a user