ToB企服应用市场:ToB评测及商务社交产业平台

标题: 14. 从零开始编写一个wmproxy(代理,内网穿透等), HTTP文件服务器的实现过 [打印本页]

作者: 农民    时间: 2023-11-8 21:58
标题: 14. 从零开始编写一个wmproxy(代理,内网穿透等), HTTP文件服务器的实现过
用Rust手把手编写一个wmproxy(代理,内网穿透等), HTTP中的压缩gzip,deflate,brotli算法

项目 ++wmproxy++

gite: https://gitee.com/tickbh/wmproxy
github: https://github.com/tickbh/wmproxy
HTTP文件服务器的意义

HTTP文件服务器的意义是可以放置网站文件,可以放置数据文件
HTTP服务器一般指网站服务器,是指驻留于因特网上某种类型计算机的程序,可以处理浏览器等Web客户端的请求并返回相应响应。
当前大量的应用会依赖到文件服务器,比如我们非常熟悉的网站(会加载index.html)文件及各种css及js文件,比如我们的各种APP会有相对应的版本信息,会有相应的版本文件,又或者小程序本身就是一个可执行文件,当你点击的时候,应用去下载相应的小程序文件,然后在本地进行加载,然后打开提供服务。目前我们的互联网上冲浪完全无法离开文件服务器。
HTTP文件服务器几大作用

1. 文件共享
文件服务器的主要功能是提供文件共享功能。它允许用户从他们自己的计算机或设备访问共享文件和文件夹,而不管他们的物理位置。用户可以查看、编辑和保存存储在服务器上的文件,所有有权访问该文件的用户都会自动更新更改。
以下是我们在聊天软件上发送一张图片给另一个人的流程
flowchart TDA[你]B[APP]C[文件服务器]D[APP服务器]E[聊天对象]A -->|将图片共享|BB -->|将图片上传|CC -->|返回图片地址|BB -->|将图片地址推送给|DD -->|将地址通知给|EE -->|从文件服务器中获取图片|C2. 集中存储
此时你的不小心将数据删除,此时你想找回原来的图片,以下是整个过程
flowchart TDA[你]B[APP]C[文件服务器]D[APP服务器]D -->|同步旧的聊天记录, 获取图片地址|BB -->|重新下载图片|CB -->|获取完图片后推送给|A此时文件服务器担任着集中存储的角色,海量的数据将汇聚在中心服务器上,我们可以通过网络访问到海量的数据资源。
3. 备份与恢复
上述过程,相当于服务器帮你备份了图片数据,在你不小心丢失的时候,可以恢复您的数据,我们最经常使用的如图片备份到网盘,一方面可以释放掉本地的空间,另一方面我们可以将数据保存到很久之后。
4. 访问控制
我们在获取到图片地址的时候,并不是任何的角色都可以获取到该图片的资源,在服务器内部中,会有相关的权限验证,在为您提供数据的同时,并保护着您的数据安全。
file_server文件服务器

一个静态文件服务器,支持真实和虚拟文件系统。它通过将请求的URI路径附加到站点的根路径来形成文件路径。
最常见的是,file_server指令与root指令配对,为整个网站设置文件根。其中保证所有的访问仅能在root指定的目录之下,不能访问其上级的任何数据,故在root下的目录理论上即使禁目录访问也可能被全部访问到(暴力遍历),但在root上级的目录不可能被以任何的方式进行访问,即使添加../相对路径也不行。
file_server参数相关

结构定义如下:
  1. pub struct FileServer {
  2.     #[serde(default = "default_root")]
  3.     pub root: String,
  4.     #[serde(default)]
  5.     pub prefix: String,
  6.     #[serde(default="default_hide")]
  7.     pub hide: Vec<String>,
  8.     #[serde(default = "default_index")]
  9.     pub index: Vec<String>,
  10.     #[serde(default = "default_status")]
  11.     pub status: u16,
  12.     #[serde(default = "default_precompressed")]
  13.     pub precompressed: Vec<String>,
  14.     #[serde(default)]
  15.     pub disable_compress: bool,
  16.     #[serde(default = "default_bool_true")]
  17.     pub browse: bool,
  18. }
复制代码

  1. reverse:
  2.   file_server:
复制代码
  1. reverse:
  2.   file_server:    browse: true
复制代码
  1. reverse:
  2.   file_server:    root: /static/    browse: true
复制代码
  1. reverse:
  2.   file_server:    root: /static/    browse: true    hide: [.git]
复制代码
  1. reverse:
  2.   file_server:    root: /static/    browse: true    hide: [.git]    precompressed: [br, gzip]
复制代码
mimetype作用

多用途互联网邮件扩展(MIME,Multipurpose Internet Mail Extensions)是一个 互联网标准,它扩展了 电子邮件标准,使其能够支持非 ASCII字符、 二进制格式附件等多种格式的邮件消息。
内容类型(Content-Type),这个头部领域用于指定消息的类型。一般以下面的形式出现。[type]/[subtype]
type有下面的形式。
Text:用于标准化地表示的文本信息,文本消息可以是多种字符集和或者多种格式的;
Multipart:用于连接消息体的多个部分构成一个消息,这些部分可以是不同类型的数据;
Application:用于传输应用程序数据或者二进制数据;
Message:用于包装一个E-mail消息;
Image:用于传输静态图片数据;
Audio:用于传输音频或者音声数据;
Video:用于传输动态影像数据,可以是与音频编辑在一起的视频数据格式。
subtype用于指定type的详细形式。type/subtype配对的集合和与此相关的参数,将随着时间而增长。为了确保这些值在一个有序而且公开的状态下开发,MIME使用Internet Assigned Numbers Authority (IANA)作为中心的注册机制来管理这些值。常用的subtype值如下所示:

我们根据现有的已知的,我们用了静态变量做了以下数据定义,后续将会进行数据补充或者自定义
  1. lazy_static! {
  2.     static ref DEFAULT_MIMETYPE: HashMap<&'static str, &'static str> = {
  3.         let mut m = HashMap::<&'static str, &'static str>::new();
  4.         m.insert("doc", "application/msword");
  5.         m.insert("pdf", "application/pdf");
  6.         m.insert("rtf", "application/rtf");
  7.         m.insert("xls", "application/vnd.ms-excel");
  8.         m.insert("ppt", "application/vnd.ms-powerpoint");
  9.         m.insert("rar", "application/application/x-rar-compressed");
  10.         m.insert("swf", "application/x-shockwave-flash");
  11.         m.insert("zip", "application/zip");
  12.         m.insert("json", "application/json");
  13.         m.insert("yaml", "text/plain");
  14.         m.insert("mid", "audio/midi");
  15.         m.insert("midi", "audio/midi");
  16.         m.insert("kar", "audio/midi");
  17.         m.insert("mp3", "audio/mpeg");
  18.         m.insert("ogg", "audio/ogg");
  19.         m.insert("m4a", "audio/m4a");
  20.         m.insert("ra", "audio/x-realaudio");
  21.         m.insert("gif", "image/gif");
  22.         m.insert("jpeg", "image/jpeg");
  23.         m.insert("jpg", "image/jpeg");
  24.         m.insert("png", "image/png");
  25.         m.insert("tif", "image/tiff");
  26.         m.insert("tiff", "image/tiff");
  27.         m.insert("wbmp", "image/vnd.wap.wbmp");
  28.         m.insert("ico", "image/x-icon");
  29.         m.insert("jng", "image/x-jng");
  30.         m.insert("bmp", "image/x-ms-bmp");
  31.         m.insert("svg", "image/svg+xml");
  32.         m.insert("svgz", "image/svg+xml");
  33.         m.insert("webp", "image/webp");
  34.         m.insert("svg", "image/svg+xml");
  35.         m.insert("css", "text/css");
  36.         m.insert("html", "text/html");
  37.         m.insert("htm", "text/html");
  38.         m.insert("shtml", "text/html");
  39.         m.insert("txt", "text/plain");
  40.         m.insert("md", "text/plain");
  41.         m.insert("xml", "text/xml");
  42.         m.insert("3gpp", "video/3gpp");
  43.         m.insert("3gp", "video/3gpp");
  44.         m.insert("mp4", "video/mp4");
  45.         m.insert("mpeg", "video/mpeg");
  46.         m.insert("mpg", "video/mpeg");
  47.         m.insert("mov", "video/quicktime");
  48.         m.insert("webm", "video/webm");
  49.         m.insert("flv", "video/x-flv");
  50.         m.insert("m4v", "video/x-m4v");
  51.         m.insert("wmv", "video/x-ms-wmv");
  52.         m.insert("avi", "video/x-msvideo");
  53.         m
  54.     };
  55. }
复制代码
源码实现

源码主要实现在file_server.rs的deal_request函数。节选
  1. pub async fn deal_request(
  2.     &self,
  3.     req: Request<RecvStream>,
  4. ) -> ProtResult<Response<RecvStream>> {
  5.     let path = req.path().clone();
  6.     // 无效前缀,无法处理
  7.     if !path.starts_with(&self.prefix) {
  8.         return Ok(self.ret_error_msg("unknow path"));
  9.     }
  10.     let root_path = Path::new(&self.root);
  11.     let mut real_path = Path::new(&real_path).to_owned();
  12.     // 必须保证不会跑出root设置的目录之外,如故意访问`../`之类的
  13.     if !real_path.starts_with(root_path) || self.is_hide_path(root_path.as_ref()) {
  14.         return Ok(self.ret_error_msg("can't view parent file"));
  15.     }
  16.     // 访问路径是目录,尝试是否有index的文件,如果有还是以文件访问
  17.     if real_path.is_dir() {
  18.         for index in &self.index {
  19.             let new_path = real_path.join(index);
  20.             if new_path.exists() {
  21.                 real_path = new_path;
  22.                 break;
  23.             }
  24.         }
  25.     }
  26.     // 访问为目录,如果启用目录访问,则返回当前的文件夹的内容
  27.     if real_path.is_dir() {
  28.         if !self.browse {
  29.             return Ok(self.ret_error_msg("can't view parent file"));
  30.         }
  31.         let mut binary = BinaryMut::new();
  32.         // ...
  33.         let recv = RecvStream::only(binary.freeze());
  34.         let builder = Response::builder().version(req.version().clone());
  35.         let mut response = builder
  36.             .header(HeaderName::CONTENT_TYPE, "text/html; charset=utf-8")
  37.             .body(recv)
  38.             .map_err(|_err| io::Error::new(io::ErrorKind::Other, ""))?;
  39.         if self.disable_compress {
  40.             response.headers_mut().insert(HeaderName::CONTENT_ENCODING, "");
  41.         }
  42.         return Ok(response);
  43.     } else {
  44.         // 访问为文件,判断当前的后缀,返回合适的mimetype,如果有合适的预压缩文件,也及时返回
  45.         if self.is_hide_path(path.as_ref()) {
  46.             return Ok(self.ret_error_msg("can't view file"));
  47.         }
  48.         // 获取后缀
  49.         let extension = if let Some(s) = real_path.extension() {
  50.             s.to_string_lossy().to_string()
  51.         } else {
  52.             String::new()
  53.         };
  54.         let application = DEFAULT_MIMETYPE.get(&*extension).unwrap_or(&"");
  55.         //查找是否有合适的预压缩文件
  56.         if let Some(accept) = req.headers().get_option_value(&HeaderName::ACCEPT_ENCODING) {
  57.             for pre in &self.precompressed {
  58.                 // 得客户端发送支持该格式
  59.                 if !accept.contains(pre.as_bytes()) {
  60.                     continue;
  61.                 }
  62.                 let mut new = real_path.clone();
  63.                 new.as_mut_os_string().push(".");
  64.                 match &**pre {
  65.                     "gzip" => new.as_mut_os_string().push("gz"),
  66.                     "br" => new.as_mut_os_string().push("br"),
  67.                     _ => continue,
  68.                 };
  69.                 // 如果预压缩文件存在
  70.                 if new.exists() {
  71.                     println!("convert to new file {}", new.to_string_lossy());
  72.                     let file = File::open(new).await?;
  73.                     let mut recv = RecvStream::new_file(file, BinaryMut::new(), false);
  74.                     match &**pre {
  75.                         "gzip" => recv.set_compress_origin_gzip(),
  76.                         "br" => recv.set_compress_brotli(),
  77.                         _ => unreachable!(),
  78.                     }
  79.                     // ...
  80.                     return Ok(response);
  81.                 }
  82.             }
  83.         }
  84.         if !real_path.exists() {
  85.             return Ok(self.ret_error_msg("can't view file"));
  86.         }
  87.         // ...
  88.         return Ok(response);
  89.     }
  90. }
复制代码
结语

如此静态文件服务器则已初步实现,文件服务中的压缩及流式传输已基本完成

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4