45从零开始用Rust编写nginx,静态文件服务器竟然还有这些细节
wmproxy
wmproxy
已用Rust
实现http/https
代理,socks5
代理, websocket
代理,反向代理, 静态文件服务器,四层TCP/UDP转发,七层负载均衡,内网穿透等,力争打造和nginx的性能。
项目地址
国内: https://gitee.com/tickbh/wmproxy
github: https://github.com/tickbh/wmproxy
静态文件服务器
静态文件服务器是一种用于提供静态文件(如HTML、CSS、JavaScript、图片等)的网络服务器。当客户端(如浏览器)请求这些文件时,静态文件服务器会直接从文件系统中获取文件并返回给客户端,而不需要经过任何处理或动态生成。
静态文件服务器的主要特点包括:
- 简单性:静态文件服务器不需要复杂的逻辑或数据库支持,只需要能够读取和发送文件即可。
- 高效性:由于不需要处理复杂的逻辑或动态生成内容,静态文件服务器通常能够更快地响应客户端请求。
- 可扩展性:静态文件服务器可以轻松地通过增加服务器数量或优化服务器配置来扩展其处理能力。
设计时注意要点
以下是此次设计时的两个注意要点:
断点续传支持:静态资源服务器通常支持断点续传。当用户下载大文件时,如果出现网络中断或其他原因导致下载中断,静态资源服务器可以记录中断位置,当用户重新请求下载时,可以恢复到中断的位置继续下载,提供更好的下载体验。
缓存和浏览器缓存支持:静态资源服务器可以通过设置合适的缓存策略,利用浏览器缓存来提高性能和减少网络流量。静态资源可以设置缓存过期时间,当浏览器再次请求相同资源时,可以直接从缓存中获取,减少了网络请求和传输时间。
启动文件服务器
对当前项目启动8080端口监听
wmproxy file-server -l :8080
缓存和浏览器缓存支持
浏览器缓存是一种机制,它将已访问过的资源的副本存储在浏览器中,以便在将来更快地加载相同的资源。可以提高网页性能、减轻服务器负担、节省网络带宽并提供更好的用户体验。在开发和优化网站时,合理地利用浏览器缓存可以显著提升网站的整体性能。
一个文件是否被修改过主要依靠以下两个属性:
Etag(Entity Tag):
- Etag是一个HTTP首部字段,用于验证浏览器缓存的组件与从服务器上获取的组件是否一致。
- 它是一个由服务器生成的唯一标识符,通常基于文件内容或某些其他属性通过特定算法计算得出。
- 当资源发生变化时,Etag值也会改变。
- 客户端会发送一个包含If-None-Match头部的请求,其中包含之前缓存资源的Etag值。
Last-Modified:
- Last-Modified也是一个HTTP首部字段,指定资源最后一次修改的时间。
- 服务器在响应头中包含该字段,告诉浏览器该资源的最后修改时间。
- 其时间粒度通常只到秒级别,不如Etag精确。
- 客户端会发送一个包含If-Modified-Since头部的请求,其中包含之前缓存资源的最后修改时间。
通常文件服务器返回时会附带该两个参数由客户端协带进行是否读取缓存数据。
控制缓存策略:
控制过期时间主要由两种方式:
Expires:
- Expires是一个
HTTP 1.0
的头部字段,用于指定资源过期的时间。 - 它是一个日期时间值,告诉浏览器资源何时过期,过期后浏览器需要重新请求资源。
- 使用Expires的一个缺点是它基于服务器的时间,如果服务器的时间不准确,缓存可能会出问题。
- Expires是一个
Cache-Control:
- Cache-Control是一个
HTTP 1.1
的头部字段,提供了更细粒度的缓存控制。 - 它可以包含多个指令,如public、private、no-cache、max-age等,用于控制资源在浏览器缓存中的存储方式和有效期。
- Cache-Control的优先级高于Expires,当两者同时存在时,Cache-Control的设置会覆盖Expires。
- Cache-Control是一个
由于Expires的客户端时间和服务端时间可能存在的不一致,此处我们服务器不做Expires的实现,如果配置过期1d
之类,将转化成:
cache-control: max-age=86400
进行时间控制。
以下我们进行测试:
我们先对资源wmproxy.md
curl.exe http://127.0.0.1:8082/wmproxy.md -i
HTTP/1.1 200 OK
content-type: text/plain; charset=utf-8
transfer-encoding: chunked
Server: wmproxy
cache-control: max-age=1000
Date: Tue, 23 Jan 2024 09:12:05 GMT
Last-Modified: Tue, 23 Jan 2024 09:11:30 GMT
etag: 65af82c2-11c1
从返回结果中我们可以得知缓存时间1000s,最后修改时间及etag的值。让我们添加IF-NONE-MATCH进行测试
curl.exe http://127.0.0.1:8082/wmproxy.md -i -H "If-Modified-Since: Tue, 23 Jan 2024 09:11:30 GMT"
HTTP/1.1 304 Not Modified
Server: wmproxy
cache-control: max-age=1000
Date: Tue, 23 Jan 2024 09:18:58 GMT
Last-Modified: Tue, 23 Jan 2024 09:11:30 GMT
etag: 65af82c2-11c1
content-length: 0
可以尝试添加最后修改时间的
curl.exe http://127.0.0.1:8082/wmproxy.md -i -H "IF-NONE-MATCH: 65af82c2-11c1"
HTTP/1.1 304 Not Modified
Server: wmproxy
cache-control: max-age=1000
Date: Tue, 23 Jan 2024 09:18:58 GMT
Last-Modified: Tue, 23 Jan 2024 09:11:30 GMT
etag: 65af82c2-11c1
content-length: 0
一样会进行缓存,通常浏览器会将两个值做为传参一起写入,文件发生变更,将会使缓存失败,重新返回200请求
curl.exe http://127.0.0.1:8082/wmproxy.md -i -H "IF-NONE-MATCH: 65af82c2-11c1"
HTTP/1.1 200 OK
...
断点续传的支持
断点续传也就是客户端可以指定传输范围进行传输,该标准定义在RFC7233
HTTP 请求范围
HTTP 协议范围请求允许服务器只发送 HTTP 消息的一部分到客户端。范围请求在传送大的媒体文件,或者与文件下载的断点续传功能搭配使用时非常有用。
检测服务器端是否支持范围请求
假如在响应中存在 Accept-Ranges
首部(并且它的值不为"none"),那么表示该服务器支持范围请求。例如,你可以使用 cURL 发送一个 HEAD 请求来进行检测。
curl.exe -I http://127.0.0.1:8080/Cargo.toml
HTTP/1.1 200 OK
...
accept-ranges: bytes
content-length: 1565
在上面的响应中, Accept-Ranges: bytes
表示界定范围的单位是 bytes
。这里 Content-Length
也是有效信息,因为它提供了要检索的文件的完整大小。
如果返回的Accept-Ranges: none
则表示不支持,如果未返回则表示可能不支持范围请求。
从服务器端请求特定的范围
假如服务器支持范围请求的话,你可以使用 Range
首部来生成该类请求。该首部指示服务器应该返回文件的哪一或哪几部分。
单一范围
我们可以请求资源的某一部分。这次我们依然用 cURL 来进行测试。"-H" 选项可以在请求中追加一个首部行,在这个例子中,是用 Range 首部来请求图片文件的前 1024 个字节。
curl http://127.0.0.1:8080/Cargo.toml -i -H "Range: bytes=0-1023"
这样生成的请求如下:
GET /Cargo.toml HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: curl/8.0.1
Accept: */*
Range: bytes=0-1023
服务器端会返回状态码为 206 Partial Content 的响应:
HTTP/1.1 206 Partial Content
content-type: text/plain; charset=utf-8
transfer-encoding: chunked
Server: wmproxy
Date: Tue, 23 Jan 2024 07:59:20 +0000
Last-Modified: Tue, 23 Jan 2024 02:33:35 +0000
etag: 65af257f-61d
content-range: bytes 0-1023/1565
...
(binary content)
在这里,Content-Length
首部现在用来表示先前请求范围的大小(而不是整个文件的大小)。Content-Range
响应首部则表示这一部分内容在整个资源中所处的位置。
多范围查询
发起请求和单一范围类似,只是在请求的时候多个范围地址,如:
curl http://www.example.com -i -H "Range: bytes=0-50, 100-150"
返回内容为Content-Type: multipart/byteranges boundary=THIS_STRING_SEPARATES
并在body中以该字符做分隔成多数据块,如。
HTTP/1.1 206 Partial Content
Content-Type: multipart/byteranges; boundary=3d6b6a416f9b5
Content-Length: 282
--3d6b6a416f9b5
Content-Type: text/html
Content-Range: bytes 0-50/1270
<!doctype html>
<html>
<head>
<title>Example Do
--3d6b6a416f9b5
Content-Type: text/html
Content-Range: bytes 100-150/1270
eta http-equiv="Content-type" content="text/html; c
--3d6b6a416f9b5--
请求多范围要针对解析body块,相对来说数据块请求比较割裂。在HTTP2中可以多流式请求范围或者用keep-alive同时发起多个请求,相对比较难与处理数据块,暂时不做实现。
条件式范围请求
当(中断之后)重新开始请求更多资源片段的时候,必须确保自从上一个片段被接收之后该资源没有进行过修改。
通过 If-Range
请求首部可以用来生成条件式范围请求:假如条件满足的话,条件请求就会生效,服务器会返回状态码为 206 Partial
的响应,以及相应的消息主体。假如条件未能得到满足,那么就会返回状态码为 200 OK 的响应,同时返回整个资源。该首部可以与 Last-Modified 验证器或者 ETag 一起使用,但是二者不能同时使用。
If-Range: Wed, 21 Oct 2015 07:28:00 GMT
或者
If-Range: 65af257f-61d
范围请求的响应
与范围请求相关的有三种状态:
- 在请求成功的情况下,服务器会返回
206 Partial Content
状态码。 - 在请求的范围越界的情况下(范围值超过了资源的大小),服务器会返回
416 Requested Range Not Satisfiable
(请求的范围无法满足)状态码。 - 在不支持范围请求的情况下,服务器会返回
200 OK
状态码。
源码相关
关于文件服务器的相关源码均在file_server
关于时间格式由RFC2822控制,这里我们用的解析库为
chrono
。关于etag,我们这里采用的与nginx一致的算法,文件最后修改时间16进制-文件长度16进制。例:
ETag: 65af8536-11c2
文件长度为:
10进制为->4546
转为16进制->11c2
文件最后修改时间:
标准日期格式->Tue, 23 Jan 2024 09:21:58 GMT
转为秒->1706001718
转为16进制->65af8536
pub fn calc_etag(data: &Metadata) -> String {
let mut seconds = 0;
let len = data.len();
if let Ok(last) = data.modified() {
if let Ok(n) = last.duration_since(SystemTime::UNIX_EPOCH) {
seconds = n.as_secs();
}
}
format!("{:x}-{:x}", seconds, len)
}
- 关于中文目录,因为文件服务器是get请求,通常均带在path下,可能由于unicode的编码进行过转化,即
问蒙服务框架
->%E9%97%AE%E8%92%99%E6%9C%8D%E5%8A%A1%E6%A1%86%E6%9E%B6
会进行一次转码,我们在path中如果存在%
的时候,尝试进行一次转码,如果成功取新的path值。
if path.contains("%") {
if let Ok(p) = Url::url_decode(&path) {
path = p;
}
}
- 断点续传(范围查询),通过在原有的基础上增加
start_pos
及end_pos
来表示文件的起始及结束点。
#[derive(Debug)]
struct InnerReceiver {
receiver: Option<Receiver<(bool, Binary)>>,
file: Option<Box<File>>,
cache_buf: Vec<u8>,
/// 数据包大小
data_size: u64,
/// 文件专用, 起始点
start_pos: Option<u64>,
/// 文件专用, 结束点
end_pos: Option<u64>,
}
小结
本章中讲述了浏览器缓存的设计(ETAG, Last-Modified, Cache-Control, Expires)及断点续传(Accept-Ranges: bytes)的实现流程及相关的部分源码,希望可以让你更了解文件服务器内部的原理组成。
点击 [关注],[在看],[点赞] 是对作者最大的支持
45从零开始用Rust编写nginx,静态文件服务器竟然还有这些细节的更多相关文章
- [Nodejs]十分钟快速编写简单静态文件服务器
学了几天Nodejs 后我又干上了前端的活.这次遇到的问题是,我想在不同的设备上方便的查看我编写的网页,很自然的就想到要是能在本地搭建一个简单的http服务器的话,那局域网内的所有设备都可以访问了,这 ...
- 从零开始,在windows上用nodejs搭建一个静态文件服务器
从零开始,在windows上用nodejs搭建一个静态文件服务器 首先安装nodejs: 新建一个node文件夹 下载node.exe到该文件夹 下载npm然后解压到该文件夹 现在node文件夹是这样 ...
- CentOS 6(64-bit) + Nginx搭建静态文件服务器
Nginx搭建静态文件服务器 使用命令打开Nginx配置文件: sudo vim /etc/nginx/conf.d/default.conf 将配置改为: server { ...... ..... ...
- Ubuntu 14.04TLS Nginx搭建静态文件服务器
Nginx搭建静态文件服务器 [官方教程]https://www.nginx.com/resources/admin-guide/serving-static-content/ 打开Nginx的配置文 ...
- 转:nginx入门指南,快速搭建静态文件服务器和代理服务器
本文介绍 Nginx 入门基础知识,让你迅速搭建 Nginx 服务器.主要内容包括 Nginx 安装和简单使用.Nginx的简单原理.Nginx 配置文件的结构.如何使用 Nginx 来提供静态文件服 ...
- nginx配置静态文件服务器的一个特殊需求的探索和分享, nginx处理不同路径返回统一文件,nginx改写,跳转请求.
最近在做一个前后端分离的个人博客,在做自己博客的时候有个想法,本来是打算用nginx作为静态文件服务器使用,django做后端程序. 我的前端页面用vue写的,结果用组件用嗨了,发现页面列表和 详情都 ...
- bloom-server 基于 rust 编写的 rest api cache 中间件
bloom-server 基于 rust 编写的 rest api cache 中间件,他位于lb 与api worker 之间,使用redis 作为缓存内容存储, 我们需要做的就是配置proxy,同 ...
- (转)Nginx静态服务配置---详解root和alias指令
Nginx静态服务配置---详解root和alias指令 原文:https://www.jianshu.com/p/4be0d5882ec5 静态文件 Nginx以其高性能著称,常用与做前端反向代理服 ...
- Nginx静态服务配置---详解root和alias指令
Nginx静态服务配置---详解root和alias指令 静态文件 Nginx以其高性能著称,常用与做前端反向代理服务器.同时nginx也是一个高性能的静态文件服务器.通常都会把应用的静态文件使用ng ...
- ASP .Net Core 中间件的使用(一):搭建静态文件服务器/访问指定文件
前言 随着Asp .Net Core的升级迭代,很多开发者都逐渐倾向于.net core开发. .net core是一个跨平台的应用程序,可以在windows.Linux.macOS系统上进行开发和部 ...
随机推荐
- Spring Cache设计之美,你品,你细品…
摘要:Spring Cache的功能很强大,设计也非常优雅,特别适合缓存控制没有那么细致的场景,比如门户首页,偏静态展示页面,榜单等等 本文分享自华为云社区<品味 spring cache设计之 ...
- 备份批处理文件 bat 生成 date 取年时,只取到周
备份数据库文件时,发现MySQL备份生成的文件名为 [vipsoft_周三],发现是系统的日期格式问题.需调整日期格式,生成 [vipsoft_20220601.sql] mysqldump -uro ...
- 用ChatGPT,入门机器学习,太强了
入门机器学习,对大部分人来说很简单,一本书.一份课件.一套视频足矣,但是我大胆猜测很多人大概率都没有完整看完过. 所以前些天在朋友圈抱怨了一波: 我感觉所谓牛人,大佬,刨除背景机遇,其成长路上可能也仅 ...
- 100天搞定机器学习|Day59 主成分分析(PCA)原理及使用详解
数学概念 方差:用来衡量随机变量与其数学期望(均值)之间的偏离程度.统计中的方差(样本方差)是各个数据分别与其平均数之差的平方的和的平均数. $$Var(X)=\frac{1}{n}\sum(x_i- ...
- PS CJ34预算转借
一.CJ34,输入发出预算和接收预算的WBS 二.调用BAPI "-----------------------------------------@斌将军----------------- ...
- Codeforce:131A. cAPS lOCK
原题链接 ╮(╯▽╰)╭这题题目一开始没看明白,导致wa几次.如果全是大写或者出了首字母是小写其他为大写,则转换为第一个字母大写,其他的小写 ,如果不是以上两种情况则不作处理. ╮(╯▽╰)╭水题还错 ...
- Codeforces Round #730 (Div. 2) A~D题个人题解
比赛链接:Here 1543A. Exciting Bets math, 给定两个数字 \(a,b (a,b \le 1e18)\) 和以下两种操作: \(a + 1,b+1\) \(a-1,b-1\ ...
- springboot项目全局异常处理@ControllerAdvice(方式二)
SpringMVC 中 @ControllerAdvice 注解的三种使用场景! @ControllerAdvice ,很多初学者可能都没有听说过这个注解,实际上,这是一个非常有用的注解,顾名思义 ...
- vue学习笔记 八、toRef的使用
系列导航 vue学习笔记 一.环境搭建 vue学习笔记 二.环境搭建+项目创建 vue学习笔记 三.文件和目录结构 vue学习笔记 四.定义组件(组件基本结构) vue学习笔记 五.创建子组件实例 v ...
- python常见面试题讲解(五)质数因子
题目描述 功能:输入一个正整数,按照从小到大的顺序输出它的所有质因子(重复的也要列举)(如180的质因子为2 2 3 3 5 ) 最后一个数后面也要有空格 输入描述: 输入一个long型整数 输出描述 ...