br-web 0.6.0

This is an WEB SERVER
Documentation
use std::fs;
use std::path::Path;
use chrono::{Utc};
use json::JsonValue;
use log::{error};
use crate::config::Config;
use crate::request::Protocol;

/// 响应处理
#[derive(Clone, Debug)]
pub struct Response {
    config: Config,
    /// 协议
    protocol: Protocol,
    code: usize,
    /// 响应正文类型
    content_type: String,
    /// 正文使用的编码
    content_charset: String,
    response: Vec<String>,
}

impl Response {
    pub fn new(config: Config, protocol: Protocol) -> Response {
        Self {
            config,
            protocol,
            code: 0,
            content_type: "text/plain".to_string(),
            content_charset: "UTF-8".to_string(),
            response: vec![],
        }
    }
    pub fn set_code(&mut self, code: usize) -> &mut Self {
        self.response = vec![];
        self.code = code;
        let msg = match code {
            100 => "Continue",
            101 => "Switching Protocols",
            102 => "Processing",
            200 => "OK",
            201 => "Created",
            301 => "Permanently Moved",// 重定向
            302 => "Move temporarily",
            403 => "Forbidden",
            404 => "Not Found",//服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面
            500 => "Internal Server Error",//服务器内部错误,无法完成请求
            501 => "Not Implemented",//服务器不支持请求的功能,无法完成请求
            502 => "Bad Gateway",//作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应
            503 => "Service Unavailable",//由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中
            504 => "Gateway Time-out",//充当网关或代理的服务器,未及时从远端服务器获取请求
            505 => "HTTP Version Not Supported",//服务器不支持请求的HTTP协议的版本,无法完成处理
            _ => {
                self.code = 505;
                "HTTP Version Not Supported"
            }
        };
        self.response.push(format!("{} {} {}", self.protocol.str(), self.code, msg));
        self.response.push(format!("Date: {}", Utc::now().format("%a, %d %b %Y %H:%M:%S GMT")));
        self.response.push("Server: Blue Rain Rust".to_string());
        self
    }
    pub fn set_access_control_allow_origin(&mut self) -> &mut Self {
        self.response.push(format!("Access-Control-Allow-Origin: {}", "*"));
        self
    }
    pub fn set_cache_control(&mut self, max_age: i32) -> &mut Self {
        self.response.push(format!("Cache-Control: no-cache,max-age={}", max_age));
        self
    }
    fn set_cors_allow_origin(&mut self) {
        if self.config.cors.allow_origin.is_empty() {
            self.response.push(format!("Access-Control-Allow-Origin: {}", "*"));
        } else {
            for origin in self.config.cors.allow_origin.iter() {
                self.response.push(format!("Access-Control-Allow-Origin: {}", origin));
            }
        }
    }
    fn set_cors_allow_credentials(&mut self) {
        if self.config.cors.allow_credentials {
            self.response.push(format!("Access-Control-Allow-Credentials: {}", self.config.cors.allow_credentials));
        }
    }
    /// options 响应
    pub fn options(&mut self) -> String {
        self.set_code(200);
        self.set_cors_allow_origin();
        self.set_cors_allow_credentials();
        self.response.push(format!("Access-Control-Allow-Methods: {}", self.config.cors.allow_methods));
        self.response.push(format!("Access-Control-Allow-Headers: {}", self.config.cors.allow_headers));
        self.response.push(format!("Access-Control-Expose-Headers: {}", self.config.cors.expose_headers));
        self.response.push(format!("Access-Control-Max-Age: {}\r\n\r\n", self.config.cors.max_age));
        self.response.join("\r\n")
    }
    /// 读取文件返回
    pub fn file(&mut self, filename: String) -> String {
        let sufxx: Vec<&str> = filename.split(".").collect();
        let sufxx = sufxx[sufxx.len() - 1];
        let file = fs::read(filename.as_str()).unwrap();
        let contents = unsafe { String::from_utf8_unchecked(file.clone()) };
        let content_type = match sufxx.to_lowercase().as_str() {
            "jpg" => "image/jpg",
            "png" => "image/png",
            "bmp" => "image/bmp",
            "jpeg" => "image/jpeg",
            "svg" => "image/svg+xml",
            "webp" => "image/webp",
            "ico" => "image/ico",
            "gif" => "image/gif",
            "avif" => "image/avif",
            "pdf" => "application/pdf",
            "xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
            "xls" => "application/vnd.ms-excel",
            "xml" => "text/xml",
            "json" => "application/json",
            "html" => "text/html;charset=utf-8",
            _ => "text/plain;charset=utf-8"
        };
        self.set_code(200);
        self.response.push("Connection: keep-alive".to_string());
        self.response.push(format!("Content-Type: {}", content_type));
        self.response.push("Cache-Control: private,max-age=0".to_string());
        self.response.push("Strict-Transport-Security: max-age=0; includeSubDomains;".to_string());
        self.response.push(format!("ETag: {}", br_crypto::hash::str_to_md5(&contents.to_string())));
        self.response.push(format!("Content-Length: {}\r\n", contents.len()));
        self.response.push(contents.to_string());
        self.response.join("\r\n")
    }
    pub fn receipt(&mut self, content_type: &str, content: JsonValue) -> String {
        self.content_type = content_type.to_string();
        self.set_code(200);
        self.set_cors_allow_origin();
        // self.response.push(format!("Access-Control-Allow-Methods: {}", self.config.cors.allow_methods));
        // self.response.push(format!("Access-Control-Allow-Headers: {}", self.config.cors.allow_headers));
        // self.response.push(format!("Access-Control-Expose-Headers: {}", self.config.cors.expose_headers));
        match self.content_type.as_str() {
            "json" => {
                self.response.push("Connection: keep-alive".to_string());
                self.response.push("Cache-Control: no-cache,max-age=0".to_string());
                self.response.push(format!("Content-type: application/json;charset={}", self.content_charset));
                self.response.push(format!("Content-Length: {}\r\n", content.to_string().len()));
                self.response.push(content.to_string());
            }
            "text" => {
                self.response.push("Connection: keep-alive".to_string());
                self.response.push(format!("Access-Control-Max-Age: {}", self.config.cors.max_age));
                self.response.push("Content-type: text/plain;charset=utf-8".to_string());
                self.response.push(format!("Content-Length: {}\r\n", content.to_string().len()));
                self.response.push(content.to_string());
            }
            "html" => {
                self.response.push("Connection: keep-alive".to_string());
                self.response.push(format!("Access-Control-Max-Age: {}", self.config.cors.max_age));
                self.response.push("Content-type: text/html;charset=utf-8".to_string());
                self.response.push(format!("Content-Length: {}\r\n", content.to_string().len()));
                self.response.push(content.to_string());
            }
            "url" => {
                self.set_code(301);
                self.set_cors_allow_origin();
                self.response.push("Connection: Close".to_string());
                self.response.push("Cache-control: no-cache".to_string());
                self.response.push(format!("Location: {}", content));
                self.response.push(format!("\r\n{}", ""));
            }
            "download" => {
                self.response.push("Connection: keep-alive".to_string());
                self.response.push("Cache-Control: no-cache,max-age=0".to_string());
                self.response.push("Content-type: application/octet-stream".to_string());
                self.response.push(format!("Access-Control-Expose-Headers: {}", self.config.cors.expose_headers));

                let path = Path::new(content.as_str().unwrap());
                match fs::read(path.to_str().unwrap()) {
                    Ok(file) => {
                        let filename = path.file_name().unwrap();
                        let filename = br_crypto::encoding::urlencoding_encode(filename.to_str().unwrap());
                        self.response.push(format!("Content-Disposition: attachment; filename={}", filename));
                        let content = unsafe { String::from_utf8_unchecked(file.clone()) };
                        self.response.push(format!("Content-Length: {}\r\n", content.len()));
                        self.response.push(content);
                    }
                    Err(_) => {
                        self.response.push(format!("\r\n{}", ""));
                    }
                }
            }
            "download_delete_dir" => {
                self.response.push("Connection: keep-alive".to_string());
                self.response.push("Cache-Control: no-cache,max-age=0".to_string());
                self.response.push("Content-type: application/octet-stream".to_string());
                self.response.push(format!("Access-Control-Expose-Headers: {}", self.config.cors.expose_headers));

                let path = Path::new(content.as_str().unwrap());
                match fs::read(path.to_str().unwrap()) {
                    Ok(file) => {
                        let filename = path.file_name().unwrap();
                        let filename = br_crypto::encoding::urlencoding_encode(filename.to_str().unwrap());
                        self.response.push(format!("Content-Disposition: attachment; filename={}", filename));
                        let content = unsafe { String::from_utf8_unchecked(file.clone()) };

                        match fs::remove_dir_all(path.parent().unwrap()) {
                            Ok(_) => {
                                self.response.push(format!("Content-Length: {}\r\n", content.len()));
                                self.response.push(content);
                            }
                            Err(e) => {
                                error!("{}", format!("文件下载并删除文件夹失败: {}", e));
                                self.response.push(format!("\r\n{}", ""));
                            }
                        }
                    }
                    Err(_) => {
                        self.response.push(format!("\r\n{}", ""));
                    }
                }
            }
            _ => {
                self.response.push("Connection: keep-alive".to_string());
                self.response.push("Cache-Control: no-cache,max-age=0".to_string());
                self.response.push("Content-type: text/html;charset=utf-8".to_string());
            }
        }

        self.response.join("\r\n")
    }

    pub fn websocket(&mut self, key: String) -> String {
        self.response = vec![];
        self.response.push("HTTP/1.1 101 Switching Protocols".to_string());
        self.response.push("Upgrade: websocket".to_string());
        self.response.push("Connection: Upgrade".to_string());

        let sha_code = br_crypto::hmac::sha_1(format!("{}258EAFA5-E914-47DA-95CA-C5AB0DC85B11", key).as_str());
        let sec_websocket_accept = br_crypto::base64::encode_file(sha_code);

        self.response.push(format!("Sec-WebSocket-Accept: {}", sec_websocket_accept));
        self.response.push("\r\n".to_string());
        self.response.join("\r\n")
    }
}