wmproxy

wmproxy将用Rust实现http/https代理, socks5代理, 反向代理, 静态文件服务器,后续将实现websocket代理, 内外网穿透等, 会将实现过程分享出来, 感兴趣的可以一起造个轮子

项目 ++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 TD
A[你]
B[APP]
C[文件服务器]
D[APP服务器]
E[聊天对象]

A -->|将图片共享|B
B -->|将图片上传|C
C -->|返回图片地址|B
B -->|将图片地址推送给|D
D -->|将地址通知给|E
E -->|从文件服务器中获取图片|C

2. 集中存储

此时你的不小心将数据删除,此时你想找回原来的图片,以下是整个过程

flowchart TD
A[你]
B[APP]
C[文件服务器]
D[APP服务器]

D -->|同步旧的聊天记录, 获取图片地址|B
B -->|重新下载图片|C
B -->|获取完图片后推送给|A

此时文件服务器担任着集中存储的角色,海量的数据将汇聚在中心服务器上,我们可以通过网络访问到海量的数据资源。

3. 备份与恢复

上述过程,相当于服务器帮你备份了图片数据,在你不小心丢失的时候,可以恢复您的数据,我们最经常使用的如图片备份到网盘,一方面可以释放掉本地的空间,另一方面我们可以将数据保存到很久之后。

4. 访问控制

我们在获取到图片地址的时候,并不是任何的角色都可以获取到该图片的资源,在服务器内部中,会有相关的权限验证,在为您提供数据的同时,并保护着您的数据安全。

file_server文件服务器

一个静态文件服务器,支持真实和虚拟文件系统。它通过将请求的URI路径附加到站点的根路径来形成文件路径。

最常见的是,file_server指令与root指令配对,为整个网站设置文件根。其中保证所有的访问仅能在root指定的目录之下,不能访问其上级的任何数据,故在root下的目录理论上即使禁目录访问也可能被全部访问到(暴力遍历),但在root上级的目录不可能被以任何的方式进行访问,即使添加../相对路径也不行。

file_server参数相关

结构定义如下:

pub struct FileServer {
#[serde(default = "default_root")]
pub root: String,
#[serde(default)]
pub prefix: String,
#[serde(default="default_hide")]
pub hide: Vec<String>,
#[serde(default = "default_index")]
pub index: Vec<String>,
#[serde(default = "default_status")]
pub status: u16,
#[serde(default = "default_precompressed")]
pub precompressed: Vec<String>,
#[serde(default)]
pub disable_compress: bool,
#[serde(default = "default_bool_true")]
pub browse: bool,
}
  • browse 对没有索引文件的目录的请求,当前又是一个目录的情况下启用文件列表。
  • root 设置网站根目录。指向的是当前文件磁盘下的路径前缀,如/file/,那么提供服务的将是/file/的文件服务
  • prefix Url的前缀,如/static/,如果我们获取到一个请求路径如/static/src/wmproxy.md,那么我们会去掉前缀得到src/wmproxy.md,那么实际的指向为/file/src/wmproxy.md进行文件服务
  • hide 是一个要隐藏的文件或文件夹的列表;如果要求,文件服务器将假装它们不存在。该指令接受占位符和glob模式。注意,这些是 文件系统 路径,不是请求路径。换句话说,相对路径使用当前工作目录作为基础,而不是网站根目录;所有的路径在比较之前都会被转换为绝对形式(如果可能的话)。指定一个没有路径分隔符的文件名或模式,将隐藏所有具有匹配名称的文件,无论其位置如何;
  • index 是一个寻找索引文件的文件名列表。默认:index.html index.htm
  • precompressed 是用于搜索预压缩挎包文件的编码格式列表。支持的格式有gzip(.gz),和br(.br)。所有的文件查找将首先寻找未压缩文件的存在。一旦找到,我们将以未添加之前的格式做mimetype,如README.md.gz取的是md的mimetype,也就是text/plain。并适当地设置Content-Encoding响应头。否则,将以正常的未压缩文件进行响应。如果encode指令被启用,那么如果没有预压缩,它可能会对响应进行即时压缩。如我们访问README.md,但此时目录下存在README.md.gz,那我们我们响应的是gz的文件,并设置Content-Encoding: gzip,如此做的好处,我们对该文件的任何请求,我们都无须耗任何压缩的时间,响应更快,我们可以用更高的压缩比来进行预压缩,可节省更多时间。
  • status 是一个可选的状态代码覆盖,在编写响应时使用。在用自定义错误页面响应请求时特别有用。可以是一个3位数的状态代码,例如:404。支持占位符。默认情况下,写入的状态代码通常是200,或206,用于部分内容。
  • 当前目录外的静态文件服务器。
reverse:
file_server:
  • 启用了文件列表:
reverse:
file_server:
browse: true
  • 只服务于/static文件夹中的静态文件:
reverse:
file_server:
root: /static/
browse: true
  • 隐藏所有.git文件夹及其内容。
reverse:
file_server:
root: /static/
browse: true
hide: [.git]
  • 如果客户端支持(Accept-Encoding头),发送gzip,br,则检查请求的文件是否存在预压缩的文件。因此,如果/path/to/file被请求,/path/to/file.br和/path/to/file.gz`,并提供第一个具有相应内容编码的可用文件。
reverse:
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值如下所示:

  • text/plain(纯文本)
  • text/html(HTML文档)
  • application/xhtml+xml(XHTML文档)
  • image/gif(GIF图像)
  • image/jpeg(JPEG图像)
  • image/png(PNG图像)
  • video/mpeg(MPEG动画)
  • application/octet-stream(任意的二进制数据)
  • application/pdf(PDF文档)
  • application/msword(Microsoft Word文件)
  • message/rfc822( RFC 822形式)
  • multipart/alternative(HTML邮件的HTML形式和纯文本形式,相同内容使用不同形式表示)
  • application/x-www-form-urlencoded(使用HTTP的POST方法提交的表单)
  • multipart/form-data(同上,但主要用于表单提交时伴随文件上传的场合)

我们根据现有的已知的,我们用了静态变量做了以下数据定义,后续将会进行数据补充或者自定义

lazy_static! {
static ref DEFAULT_MIMETYPE: HashMap<&'static str, &'static str> = {
let mut m = HashMap::<&'static str, &'static str>::new();
m.insert("doc", "application/msword");
m.insert("pdf", "application/pdf");
m.insert("rtf", "application/rtf");
m.insert("xls", "application/vnd.ms-excel");
m.insert("ppt", "application/vnd.ms-powerpoint");
m.insert("rar", "application/application/x-rar-compressed");
m.insert("swf", "application/x-shockwave-flash");
m.insert("zip", "application/zip");
m.insert("json", "application/json");
m.insert("yaml", "text/plain");
m.insert("mid", "audio/midi");
m.insert("midi", "audio/midi");
m.insert("kar", "audio/midi");
m.insert("mp3", "audio/mpeg");
m.insert("ogg", "audio/ogg");
m.insert("m4a", "audio/m4a");
m.insert("ra", "audio/x-realaudio");
m.insert("gif", "image/gif");
m.insert("jpeg", "image/jpeg");
m.insert("jpg", "image/jpeg");
m.insert("png", "image/png");
m.insert("tif", "image/tiff");
m.insert("tiff", "image/tiff");
m.insert("wbmp", "image/vnd.wap.wbmp");
m.insert("ico", "image/x-icon");
m.insert("jng", "image/x-jng");
m.insert("bmp", "image/x-ms-bmp");
m.insert("svg", "image/svg+xml");
m.insert("svgz", "image/svg+xml");
m.insert("webp", "image/webp");
m.insert("svg", "image/svg+xml");
m.insert("css", "text/css");
m.insert("html", "text/html");
m.insert("htm", "text/html");
m.insert("shtml", "text/html");
m.insert("txt", "text/plain");
m.insert("md", "text/plain");
m.insert("xml", "text/xml");
m.insert("3gpp", "video/3gpp");
m.insert("3gp", "video/3gpp");
m.insert("mp4", "video/mp4");
m.insert("mpeg", "video/mpeg");
m.insert("mpg", "video/mpeg");
m.insert("mov", "video/quicktime");
m.insert("webm", "video/webm");
m.insert("flv", "video/x-flv");
m.insert("m4v", "video/x-m4v");
m.insert("wmv", "video/x-ms-wmv");
m.insert("avi", "video/x-msvideo");
m
};
}

源码实现

源码主要实现在file_server.rsdeal_request函数。节选

pub async fn deal_request(
&self,
req: Request<RecvStream>,
) -> ProtResult<Response<RecvStream>> {
let path = req.path().clone();
// 无效前缀,无法处理
if !path.starts_with(&self.prefix) {
return Ok(self.ret_error_msg("unknow path"));
}
let root_path = Path::new(&self.root);
let mut real_path = Path::new(&real_path).to_owned();
// 必须保证不会跑出root设置的目录之外,如故意访问`../`之类的
if !real_path.starts_with(root_path) || self.is_hide_path(root_path.as_ref()) {
return Ok(self.ret_error_msg("can't view parent file"));
}
// 访问路径是目录,尝试是否有index的文件,如果有还是以文件访问
if real_path.is_dir() {
for index in &self.index {
let new_path = real_path.join(index);
if new_path.exists() {
real_path = new_path;
break;
}
}
} // 访问为目录,如果启用目录访问,则返回当前的文件夹的内容
if real_path.is_dir() {
if !self.browse {
return Ok(self.ret_error_msg("can't view parent file"));
}
let mut binary = BinaryMut::new();
// ...
let recv = RecvStream::only(binary.freeze());
let builder = Response::builder().version(req.version().clone());
let mut response = builder
.header(HeaderName::CONTENT_TYPE, "text/html; charset=utf-8")
.body(recv)
.map_err(|_err| io::Error::new(io::ErrorKind::Other, ""))?;
if self.disable_compress {
response.headers_mut().insert(HeaderName::CONTENT_ENCODING, "");
}
return Ok(response);
} else { // 访问为文件,判断当前的后缀,返回合适的mimetype,如果有合适的预压缩文件,也及时返回
if self.is_hide_path(path.as_ref()) {
return Ok(self.ret_error_msg("can't view file"));
}
// 获取后缀
let extension = if let Some(s) = real_path.extension() {
s.to_string_lossy().to_string()
} else {
String::new()
};
let application = DEFAULT_MIMETYPE.get(&*extension).unwrap_or(&"");
//查找是否有合适的预压缩文件
if let Some(accept) = req.headers().get_option_value(&HeaderName::ACCEPT_ENCODING) {
for pre in &self.precompressed {
// 得客户端发送支持该格式
if !accept.contains(pre.as_bytes()) {
continue;
}
let mut new = real_path.clone();
new.as_mut_os_string().push(".");
match &**pre {
"gzip" => new.as_mut_os_string().push("gz"),
"br" => new.as_mut_os_string().push("br"),
_ => continue,
};
// 如果预压缩文件存在
if new.exists() {
println!("convert to new file {}", new.to_string_lossy());
let file = File::open(new).await?;
let mut recv = RecvStream::new_file(file, BinaryMut::new(), false);
match &**pre {
"gzip" => recv.set_compress_origin_gzip(),
"br" => recv.set_compress_brotli(),
_ => unreachable!(),
}
// ...
return Ok(response);
}
}
} if !real_path.exists() {
return Ok(self.ret_error_msg("can't view file"));
} // ...
return Ok(response);
}
}

结语

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

14. 从零开始编写一个类nginx工具, HTTP文件服务器的实现过程及参数的更多相关文章

  1. 从零开始编写一个BitTorrent下载器

    从零开始编写一个BitTorrent下载器 BT协议 简介 BT协议Bit Torrent(BT)是一种通信协议,又是一种应用程序,广泛用于对等网络通信(P2P).曾经风靡一时,由于它引起了巨大的流量 ...

  2. 22.编写一个类A,该类创建的对象可以调用方法showA输出小写的英文字母表。然后再编写一个A类的子类B,子类B创建的对象不仅可以调用方法showA输出小写的英文字母表,而且可以调用子类新增的方法showB输出大写的英文字母表。最后编写主类C,在主类的main方法 中测试类A与类B。

    22.编写一个类A,该类创建的对象可以调用方法showA输出小写的英文字母表.然后再编写一个A类的子类B,子类B创建的对象不仅可以调用方法showA输出小写的英文字母表,而且可以调用子类新增的方法sh ...

  3. 35.按要求编写Java程序: (1)编写一个接口:InterfaceA,只含有一个方法int method(int n); (2)编写一个类:ClassA来实现接口InterfaceA,实现int method(int n)接口方 法时,要求计算1到n的和; (3)编写另一个类:ClassB来实现接口InterfaceA,实现int method(int n)接口 方法时,要求计算n的阶乘(n

      35.按要求编写Java程序: (1)编写一个接口:InterfaceA,只含有一个方法int method(int n): (2)编写一个类:ClassA来实现接口InterfaceA,实现in ...

  4. 编写一个类,其中包含一个排序的方法Sort(),当传入的是一串整数,就按照从小到大的顺序输出,如果传入的是一个字符串,就将字符串反序输出。

    namespace test2 { class Program { /// <summary> /// 编写一个类,其中包含一个排序的方法Sort(),当传入的是一串整数,就按照从小到大的 ...

  5. 二、 编写一个类,用两个栈实现队列,支持队列的基本操作(add,poll,peek)

    请指教交流! package com.it.hxs.c01; import java.util.Stack; /* 编写一个类,用两个栈实现队列,支持队列的基本操作(add,poll,peek) */ ...

  6. 如何编写一个SQL注入工具

    0x01  前言 一直在思考如何编写一个自动化注入工具,这款工具不用太复杂,但是可以用最简单.最直接的方式来获取数据库信息,根据自定义构造的payload来绕过防护,这样子就可以. 0x02 SQL注 ...

  7. 从零开始编写一个vue插件

    title: 从零开始编写一个vue插件 toc: true date: 2018-12-17 10:54:29 categories: Web tags: vue mathjax 写毕设的时候需要一 ...

  8. 题目一:编写一个类Computer,类中含有一个求n的阶乘的方法

    作业:编写一个类Computer,类中含有一个求n的阶乘的方法.将该类打包,并在另一包中的Java文件App.java中引入包,在主类中定义Computer类的对象,调用求n的阶乘的方法(n值由参数决 ...

  9. 多任务-python实现-继承Thread类,单独编写一个类(2.1.2)

    @ 目录 1.thread类 1.thread类 threding代码实现 import threading import time class MyThread(threading.Thread): ...

  10. 14、编写一个通用的Makefile

    编译test_Makefile的方法:a. gcc -o test a.c b.c对于a.c: 预处理.编译(C文件转换成汇编).汇编(汇编转换成机器码)对于b.c:预处理.编译.汇编最后链接优点:命 ...

随机推荐

  1. GGTalk 开源即时通讯系统源码剖析之:数据库设计

    自从<开源即时通讯GGTalk 8.0发布,增加Linux客户端,支持在统信UOS.银河麒麟上运行!>一文在博客园发布后,有园友联系我QQ,说能不能整理个更系统更详细地介绍GGTalk源码 ...

  2. Kotlin 常用语法糖记录

    原文地址: Kotlin 常用语法糖记录 - Stars-One的杂货小窝 当使用 Kotlin 编程时,有一些常用的函数可以帮助我们简化代码并提高开发效率. 稍微列举下常用的方法 runCatchi ...

  3. Python 逻辑表达式的妙用

    今天偶然看到有这样一段代码,感到很惊讶: super().__init__(package_name or (robot_name + "_moveit_config")) 语义非 ...

  4. 详解nvim内建LSP体系与基于nvim-cmp的代码补全体系

    2023年,nvim以及其生态已经发展的愈来愈完善了.nvim内置的LSP(以及具体的语言服务)加上众多插件,可以搭建出支持各种类型语法检查.代码补全.代码格式化等功能的IDE.网络上关于如何配置的文 ...

  5. Java扩展Nginx之五:五大handler(系列最核心)

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是<Java扩展Nginx> ...

  6. Ubuntu16.04配置NTP时间同步

    环境 查看系统版本:lsb_release -a 名词解释 PDT是指太平洋夏令时(Pacific Daylight Time),是美国西部地区和加拿大的一部分地区使用的时区.它位于UTC-7和UTC ...

  7. 【技术积累】Vue.js中的组件库【一】

    Vue组件库是什么 Vue中的组件库是一组预先构建好的可重用组件,用于加速开发过程并提高代码的可维护性.组件库通常包含一系列常用的UI组件,如按钮.输入框.下拉菜单等,以及一些功能性组件,如模态框.轮 ...

  8. Redis 备忘录

    redis是什么 Redis 是一个高性能的key-value数据库 常用操作 下载 官网:https://redis.io/ Linux版:https://redis.io/download Win ...

  9. 【WebGL系列-04】清除缓冲区并绘制图形

    清除缓冲区并绘制图形 前文中已经准备好了webgl程序和绘制所用的数据,但是在绘制图像之前,还要对画布进行处理. 清除缓冲区 由于图像的绘制是一帧一帧绘制,每一帧针对当前的状态,计算屏幕上每个像素的颜 ...

  10. 一种基于ChatGPT的高效吃瓜方式的探索和研究。

    你好呀,我是歪歪. 最近掌握了一个新的吃瓜方式,我觉得还行,给大家简单分享一下. 事情说来就话长了,还得从最近的一次"工业革命"开始,也就是从超导材料说起. 8 月 1 日的时候 ...