use crate::{parse_content_type_header_value, split_boundary, Authorization, Connection, ContentType, Encoding, HttpError, Language, Method, Protocol, Upgrade, Uri};
use crate::config::{Config};
use chrono::{DateTime, Local};
use json::{array, object, JsonValue};
use log::{info};
use std::io::{Write};
use std::path::{Path};
use std::{env, fs, io, thread};
use std::cmp::PartialEq;
use std::fs::OpenOptions;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use crate::stream::{Scheme};
#[derive(Clone, Debug)]
pub struct Request {
pub config: Config,
pub header_line: String,
pub protocol: Protocol,
pub method: Method,
pub uri: Uri,
pub origin: String,
pub header: JsonValue,
pub cookie: JsonValue,
pub query: JsonValue,
pub params: JsonValue,
pub authorization: Authorization,
pub handle_time: f64,
pub datetime: String,
pub timestamp: i64,
pub client_ip: String,
pub proxy_ip: String,
pub server_ip: String,
pub upgrade: Upgrade,
pub connection: Connection,
pub accept_encoding: Encoding,
start_time: Instant,
pub body_data: Vec<u8>,
content_length: usize,
pub content_type: ContentType,
boundary: String,
pub scheme: Arc<Mutex<Scheme>>,
pub accept_language: Language,
}
impl Request {
pub fn new(config: Config, scheme: Arc<Mutex<Scheme>>) -> Self {
let client_ip = scheme.lock().unwrap().client_ip();
let server_ip = scheme.lock().unwrap().server_ip();
let local: DateTime<Local> = Local::now();
Self {
config,
header_line: String::new(),
protocol: Protocol::Other(String::new()),
method: Method::Other(String::new()),
uri: Uri::default(),
origin: String::new(),
header: object! {},
cookie: object! {},
query: object! {},
params: object! {},
authorization: Authorization::Other(String::new()),
handle_time: 0.0,
scheme,
start_time: Instant::now(),
datetime: local.format("%Y-%m-%d %H:%M:%S").to_string(),
timestamp: local.timestamp(),
client_ip,
server_ip,
proxy_ip: String::new(),
upgrade: Upgrade::Other(String::new()),
connection: Connection::Other(String::new()),
accept_encoding: Encoding::None,
body_data: vec![],
content_length: 0,
content_type: ContentType::Other(String::new()),
boundary: String::new(),
accept_language: Language::ZhCN,
}
}
pub fn handle(&mut self) -> Result<(), HttpError> {
let mut data = vec![];
{
self.scheme.lock().unwrap().read(&mut data)?;
if let Some(pos) = data.windows(2).position(|window| window == [13, 10]) {
let header_data = data.drain(..pos).collect::<Vec<u8>>();
let header_data = String::from_utf8_lossy(header_data.as_slice());
data.drain(..2);
self.handle_header_line(header_data.trim())?;
} else {
return Err(HttpError::new(400, "请求行错误"));
}
}
match &self.protocol {
Protocol::HTTP1_0 | Protocol::HTTP1_1 => {
{
loop {
if let Some(pos) = data.windows(4).position(|window| window == [13, 10, 13, 10]) {
self.handle_header(data.drain(..pos).collect::<Vec<u8>>())?;
data.drain(..4);
self.body_data = data;
break;
}
self.scheme.lock().unwrap().read(&mut data)?;
}
}
{
if self.content_length > 0 {
loop {
if self.body_data.len() >= self.content_length {
break;
}
self.scheme.lock().unwrap().read(&mut self.body_data)?;
}
self.handle_body(self.body_data.clone())?;
}
}
self.handle_time = self.start_time.elapsed().as_micros() as f64 / 1000.0;
}
Protocol::HTTP2 => {
let header = data.drain(..8).collect::<Vec<u8>>();
if header.ne(b"\r\nSM\r\n\r\n") {
return Err(HttpError::new(400, "HTTP2格式错误"));
}
self.scheme.lock().unwrap().http2_send_server_settings()?;
let scheme_arc = self.scheme.clone();
let mut scheme = scheme_arc.lock().unwrap();
scheme.http2_handle_header(&mut data, self)?;
self.body_data = scheme.http2_handle_body(&mut data, self.clone())?;
self.handle_body(self.body_data.clone())?;
self.handle_time = self.start_time.elapsed().as_micros() as f64 / 1000.0;
}
Protocol::HTTP3 => return Err(HttpError::new(500, format!("未支持: HTTP3 {:?}", self.protocol).as_str())),
Protocol::Other(e) => return Err(HttpError::new(500, format!("未支持: Other {e} {:?}", self.protocol).as_str())),
}
Ok(())
}
pub fn handle_header_line(&mut self, line: &str) -> Result<(), HttpError> {
self.header_line = br_crypto::encoding::urlencoding_decode(line);
if self.header_line.is_empty() {
return Err(HttpError::new(400, "请求行错误"));
}
let mut it = self.header_line.split_whitespace();
let method = it.next();
let target = it.next();
let version = it.next();
self.protocol = match version {
None => return Err(HttpError::new(400, "协议版本错误")),
Some(e) => Protocol::from(e)
};
match &self.protocol {
Protocol::HTTP1_0 => {
self.method = match method {
None => return Err(HttpError::new(400, "HTTP10请求类型错误")),
Some(e) => Method::from(e)
};
self.uri = match target {
None => return Err(HttpError::new(400, "HTTP10请求资源错误")),
Some(e) => Uri::from(e)
};
self.query = self.uri.get_query_params();
}
Protocol::HTTP1_1 => {
self.method = match method {
None => return Err(HttpError::new(400, "HTTP11请求类型错误")),
Some(e) => Method::from(e)
};
self.uri = match target {
None => return Err(HttpError::new(400, "HTTP11请求资源错误")),
Some(e) => Uri::from(e)
};
self.query = self.uri.get_query_params();
}
Protocol::HTTP2 => {}
Protocol::HTTP3 => return Err(HttpError::new(400, format!("{:?}协议暂未实现", self.protocol).as_str())),
Protocol::Other(name) => return Err(HttpError::new(400, format!("{name}协议暂未实现").as_str())),
}
Ok(())
}
pub fn handle_header(&mut self, data: Vec<u8>) -> Result<(), HttpError> {
let headers = String::from_utf8_lossy(data.as_slice());
if self.config.debug {
info!("\r\n=================请求头 {:?}=================\r\n{}\r\n{headers}\r\n========================================",thread::current().id(),self.header_line);
}
match &self.protocol {
Protocol::HTTP1_0 => {
for item in headers.lines() {
self.header_line_set(item)?;
}
}
Protocol::HTTP1_1 => {
for item in headers.lines() {
self.header_line_set(item)?;
}
if !self.header.has_key("host") {
return Err(HttpError::new(400, "请求头错误"));
}
}
Protocol::HTTP2 => {
return Err(HttpError::new(400, "HTTP2格式错误"));
}
Protocol::HTTP3 => return Err(HttpError::new(400, "暂时未开放")),
Protocol::Other(name) => {
return Err(HttpError::new(400, format!("未知协议格式: {}", name).as_str()));
}
}
Ok(())
}
fn header_line_set(&mut self, line: &str) -> Result<(), HttpError> {
match line.trim().find(":") {
None => return Err(HttpError::new(400, format!("请求头[{line}]错误").as_str())),
Some(e) => {
let key = line[..e].trim().to_lowercase().clone();
let value = line[e + 1..].trim();
self.set_header(key.as_str(), value)?;
}
}
Ok(())
}
pub fn set_header(&mut self, key: &str, value: &str) -> Result<(), HttpError> {
self.header[key] = value.into();
if value.len() > 8192 {
return Err(HttpError::new(400, "header longer than 8192 characters"));
}
match key {
"origin" => self.origin = value.to_string(),
"content-type" => {
let (mime, params) = parse_content_type_header_value(value);
if !mime.is_empty() {
if mime == "multipart/form-data" {
if let Some(b) = params.get("boundary") {
self.boundary = b.to_string();
} else {
let lower = value.to_lowercase();
if let Some(pos) = lower.find("boundary=") {
let raw = &value[pos + "boundary=".len()..];
let raw = raw.split(';').next().unwrap_or(raw).trim();
let raw = raw.trim_matches('"');
self.boundary = raw.to_string();
}
}
self.content_type = ContentType::from("multipart/form-data");
} else {
self.content_type = ContentType::from(mime.as_str());
}
}
self.header[key] = self.content_type.str().into();
}
"content-length" => self.content_length = value.parse::<usize>().unwrap_or(0),
"accept-language" => self.accept_language = Language::from(value),
"authorization" => self.authorization = Authorization::from(value),
"upgrade" => self.upgrade = Upgrade::from(value),
"connection" => self.connection = Connection::from(value),
"accept-encoding" => self.accept_encoding = Encoding::from(value),
"cookie" => {
let _ = value.split(';').collect::<Vec<&str>>().iter().map(|&x| {
match x.find('=') {
None => {}
Some(index) => {
let key = x[..index].trim().to_string();
let val = x[index + 1..].trim().to_string();
let _ = self.cookie.insert(key.as_str(), val);
}
}
""
}).collect::<Vec<&str>>();
}
"x-forwarded-for" => self.proxy_ip = value.to_string(),
"x-real-ip" => self.client_ip = value.to_string(),
_ => {}
}
Ok(())
}
pub fn handle_body(&mut self, data: Vec<u8>) -> Result<(), HttpError> {
if self.config.debug {
info!("\r\n=================请求体 {:?}=================\r\n长度: {}\r\n========================================",thread::current().id(),self.content_length);
}
if data.len() != self.content_length {
return Err(HttpError::new(400, format!("Content-Length mismatch: header={}, actual={}", self.content_length, data.len()).as_str()));
}
if self.content_length == 0 {
return Ok(());
}
match &self.content_type {
ContentType::FormData => {
let parts = match split_boundary(data, &self.boundary) {
Ok(e) => e,
Err(_) => return Err(HttpError::new(400, "Invalid boundary marker"))
};
let mut fields = object! {};
for part in parts {
let (header, body) = match part.windows(b"\r\n\r\n".len()).position(|window| window == b"\r\n\r\n") {
None => continue,
Some(e) => {
let header = part[..e].to_vec();
let body = part[e + 4..].to_vec();
let body = body[..body.len() - 2].to_vec();
(header, body)
}
};
let headers = String::from_utf8_lossy(header.as_slice());
let mut field_name = "";
let mut filename = "";
let mut content_type = ContentType::Other("".to_string());
for header in headers.lines() {
if header.to_lowercase().starts_with("content-disposition:") {
match header.find("filename=\"") {
None => {}
Some(filename_start) => {
let filename_len = filename_start + 10;
let filename_end = header[filename_len..].find('"').unwrap() + filename_len;
filename = &header[filename_len..filename_end];
}
}
match header.find("name=\"") {
None => {}
Some(name_start) => {
let name_start = name_start + 6;
let name_end = header[name_start..].find('"').unwrap() + name_start;
field_name = &header[name_start..name_end];
}
}
}
if header.to_lowercase().starts_with("content-type:") {
content_type = ContentType::from(header.to_lowercase().trim_start_matches("content-type:").trim());
}
}
if filename.is_empty() {
let text = String::from_utf8_lossy(body.as_slice());
fields[field_name.to_string()] = JsonValue::from(text.into_owned());
continue;
}
let extension = Path::new(filename).extension().and_then(|ext| ext.to_str()); let suffix = extension.unwrap_or("txt");
let filename = if extension.is_none() {
format!("{filename}.txt")
} else {
filename.to_string()
};
let mut temp_dir = env::temp_dir();
temp_dir.push(filename.clone());
let Ok(mut temp_file) = fs::File::create(&temp_dir) else { continue };
if temp_file.write(body.as_slice()).is_ok() {
if fields[field_name.to_string()].is_empty() {
fields[field_name.to_string()] = array![];
}
fields[field_name.to_string()].push(object! {
id:br_crypto::sha256::encrypt_hex(&body.clone()),
name:filename,
suffix:suffix,
size:body.len(),
type:content_type.str(),
file:temp_dir.to_str()
}).unwrap();
}
}
self.params = fields;
}
ContentType::FormUrlencoded => {
let input = String::from_utf8_lossy(&data);
let mut list = object! {};
for pair in input.split('&') {
if let Some((key, val)) = pair.split_once('=') {
let key = br_crypto::encoding::urlencoding_decode(key);
let val = br_crypto::encoding::urlencoding_decode(val);
let _ = list.insert(key.as_str(), val);
}
}
self.params = list;
}
ContentType::Json => {
let text = String::from_utf8_lossy(data.as_slice());
self.params = json::parse(text.into_owned().as_str()).unwrap_or(object! {});
}
ContentType::Xml | ContentType::Html | ContentType::Text | ContentType::Javascript => {
let text = String::from_utf8_lossy(data.as_slice());
self.params = text.into_owned().into();
}
ContentType::Other(_) => {}
ContentType::Stream => {}
}
Ok(())
}
pub fn save_log(&mut self) -> io::Result<()> {
if !self.config.log {
return Ok(());
}
let local: DateTime<Local> = Local::now();
let time_dir = local.format("%Y-%m-%d-%H").to_string();
let time_dir = time_dir.split('-').collect::<Vec<&str>>();
let mut res = self.config.root_path.join(self.config.runtime.clone()).join("log");
for item in &time_dir {
res.push(item);
}
fs::create_dir_all(res.parent().unwrap())?;
let log_file = format!("{}.log", res.to_str().unwrap());
let mut file = OpenOptions::new()
.append(true) .create(true) .open(log_file)?;
let data = format!(
"[{}] {} ClientIP: {} {} {} ContentLength: {} ContentType: {} Time: {:?} Thread: {:?}\r\n",
self.datetime,
self.protocol.str(),
self.client_ip,
self.method.str(),
self.uri.url,
self.content_length,
self.content_type.clone().str(),
self.handle_time,
thread::current().id()
);
file.write_all(data.as_bytes())?;
Ok(())
}
}