use std::collections::{BTreeMap};
use std::{fs, io};
use json::{JsonValue};
use crate::request::Protocol;
use std::path::{PathBuf};
use chrono::{DateTime, Utc};
use log::{debug, warn};
use crate::base64::{encode_file, sha_1};
use crate::client::Scheme;
use crate::config::Config;
use crate::encoding::{compress_to_gzip};
#[derive(Clone, Debug)]
pub struct Response {
pub config: Config,
pub status: Status,
pub headers: BTreeMap<String, String>,
pub cookies: BTreeMap<String, String>,
pub body: Vec<u8>,
pub body_text: JsonValue,
pub content_encoding: String,
}
impl Response {
pub fn new(config: Config) -> Self {
Self {
config,
status: Status::default(),
headers: Default::default(),
cookies: Default::default(),
body: vec![],
body_text: JsonValue::Null,
content_encoding: "".to_string(),
}
}
pub fn status(&mut self, code: i32) -> &mut Self {
self.status.set_code(code);
self
}
pub fn location(&mut self, uri: &str) -> &mut Self {
self.header("Location", uri);
self
}
pub fn set_host(&mut self, host: &str) -> &mut Self {
self.header("Host", host);
self
}
pub fn set_compress(&mut self, value: Vec<String>) -> &mut Self {
let content_encoding = value.join(",");
self.content_encoding = content_encoding;
self.header("Content-Encoding", self.content_encoding.clone().as_str());
self
}
pub fn header(&mut self, key: &str, value: &str) -> &mut Self {
self.headers.insert(key.to_string(), value.to_string());
self
}
pub fn cookie(&mut self, key: &str, value: &str) -> &mut Self {
self.cookies.insert(key.to_string(), value.to_string());
self
}
pub fn set_server(&mut self, name: &str) {
self.header("Server", name);
}
pub fn update_body(&mut self) {
self.body = self.body_text.to_string().into_bytes();
}
pub fn get_date(&mut self) -> String {
let utc: DateTime<Utc> = Utc::now();
utc.format("%a, %d %b %Y %H:%M:%S GMT").to_string()
}
pub fn html(&mut self, value: &str) -> &mut Self {
self.header("Content-Type", format!("{}; charset={}", Extension::form("html").as_str(), self.config.charset).as_str());
self.body = value.to_string().into_bytes();
self.body_text = value.into();
self
}
pub fn txt(&mut self, value: &str) -> &mut Self {
self.header("Content-Type", format!("{}; charset={}", Extension::form("txt").as_str(), self.config.charset).as_str());
self.body = value.to_string().into_bytes();
self.body_text = value.into();
self
}
pub fn json(&mut self, value: JsonValue) -> &mut Self {
self.header("Content-Type", format!("{}; charset={}", Extension::form("json").as_str(), self.config.charset).as_str());
self.body = value.to_string().into_bytes();
self.body_text = value;
self
}
pub fn download(&mut self, filename: PathBuf) -> &mut Self {
let file = match fs::read(filename.clone()) {
Ok(e) => e,
Err(_) => {
self.status(404);
return self;
}
};
let extension = filename.extension().unwrap().to_str().unwrap().to_lowercase();
self.header("Content-Type", format!("{}; charset={}", Extension::form(extension.as_str()).as_str(), self.config.charset).as_str());
self.header("Content-Disposition", format!(r#"attachment; filename="{}""#, filename.file_name().unwrap().to_str().unwrap()).as_str());
let digest = md5::compute(file.clone());
self.header("Cache-Control", "no-cache");
self.header("ETag", format!("{:x}", digest).as_str());
self.body = file;
self
}
pub fn file(&mut self, filename: PathBuf) -> &mut Self {
let file = match fs::read(filename.clone()) {
Ok(e) => e,
Err(_) => {
self.status(404);
return self;
}
};
let extension = filename.extension().unwrap().to_str().unwrap().to_lowercase();
self.header("Content-Type", format!("{}; charset={}", Extension::form(extension.as_str()).as_str(), self.config.charset).as_str());
self.header("Content-Disposition", format!(r#"inline; filename="{}""#, filename.file_name().unwrap().to_str().unwrap()).as_str());
let digest = md5::compute(file.clone());
self.header("Cache-Control", "no-cache");
self.header("ETag", format!("{:x}", digest).as_str());
self.body = file;
self
}
pub fn websocket(&mut self, key: String) -> &mut Self {
self.header("Upgrade", "websocket");
self.header("Connection", "Upgrade");
let sha_code = sha_1(format!("{}258EAFA5-E914-47DA-95CA-C5AB0DC85B11", key).as_str());
let sec_websocket_accept = encode_file(sha_code);
self.header("Sec-WebSocket-Accept", sec_websocket_accept.leak());
self
}
pub fn send(&mut self, scheme: Scheme) -> io::Result<()> {
let mut header = vec![];
header.push(self.status.text());
header.push(format!("Date: {}", self.get_date()));
for (key, value) in self.headers.iter() {
header.push(format!("{}: {}", key, value))
}
for (key, value) in self.cookies.iter() {
header.push(format!("Set-Cookie: {}={}", key, value));
}
if !self.body.is_empty() {
header.push(format!("Content-Length: {}", self.body.len()));
}
let body = if !self.content_encoding.is_empty() {
match self.content_encoding.as_str() {
x if x.contains("gzip") => {
let t = compress_to_gzip(self.body.clone())?;
unsafe { String::from_utf8_unchecked(t) }
}
_ => {
unsafe { String::from_utf8_unchecked(self.body.clone()) }
}
}
} else {
unsafe { String::from_utf8_unchecked(self.body.clone()) }
};
let text = format!("{}\r\n\r\n{}", header.join("\r\n"), body);
if self.config.is_debug {
debug!("\r\n=================响应信息=================\r\n{}\r\n===========================================", text);
}
scheme.write(text.as_bytes())?;
Ok(())
}
}
impl Default for Response {
fn default() -> Self {
Self {
config: Default::default(),
status: Default::default(),
headers: Default::default(),
cookies: Default::default(),
body: vec![],
body_text: JsonValue::Null,
content_encoding: "".to_string(),
}
}
}
#[derive(Clone, Debug)]
pub struct Status {
pub code: i32,
protocol: Protocol,
reason: String,
}
impl Status {
pub fn set_code(&mut self, code: i32) {
self.code = code;
self.reason = match code {
100 => "Continue",
101 => "Switching Protocols",
102 => "Processing",
200 => "OK",
201 => "Created",
204 => "No Content",
301 => "Permanently Moved", 302 => "Move temporarily",
403 => "Forbidden",
404 => "Not Found", 500 => "Internal Server Error", 501 => "Not Implemented", 502 => "Bad Gateway", 503 => "Service Unavailable", 504 => "Gateway Time-out", 505 => "HTTP Version Not Supported", _ => ""
}.to_string()
}
pub fn text(&mut self) -> String {
format!("{} {} {}", self.protocol.str(), self.code, self.reason)
}
}
impl Default for Status {
fn default() -> Self {
Self {
code: 200,
protocol: Protocol::HTTP1_1,
reason: "OK".to_string(),
}
}
}
enum Extension {}
impl Extension {
pub fn form(extension: &str) -> String {
match extension {
"html" => "text/html",
"css" => "text/css",
"js" => "application/javascript",
"json" => "application/json",
"png" => "image/png",
"jpg" | "jpeg" => "image/jpeg",
"gif" => "image/gif",
"txt" => "text/plain",
"pdf" => "application/pdf",
"svg" => "image/svg+xml",
"woff" => "application/font-woff",
"woff2" => "application/font-woff2",
_ => {
warn!("未知 content_type: {}",extension);
"application/octet-stream"
}
}.to_string()
}
}