首页 > 系统相关 >45从零开始用Rust编写nginx,静态文件服务器竟然还有这些细节

45从零开始用Rust编写nginx,静态文件服务器竟然还有这些细节

时间:2024-01-26 09:23:37浏览次数:40  
标签:文件 缓存 HTTP 请求 45 Content nginx 服务器 Rust

wmproxy

wmproxy已用Rust实现http/https代理,socks5代理, websocket代理,反向代理, 静态文件服务器,四层TCP/UDP转发,七层负载均衡,内网穿透等,力争打造和nginx的性能。

项目地址

国内: https://gitee.com/tickbh/wmproxy

github: https://github.com/tickbh/wmproxy

静态文件服务器

静态文件服务器是一种用于提供静态文件(如HTML、CSS、JavaScript、图片等)的网络服务器。当客户端(如浏览器)请求这些文件时,静态文件服务器会直接从文件系统中获取文件并返回给客户端,而不需要经过任何处理或动态生成。

静态文件服务器的主要特点包括:

  1. 简单性:静态文件服务器不需要复杂的逻辑或数据库支持,只需要能够读取和发送文件即可。
  2. 高效性:由于不需要处理复杂的逻辑或动态生成内容,静态文件服务器通常能够更快地响应客户端请求。
  3. 可扩展性:静态文件服务器可以轻松地通过增加服务器数量或优化服务器配置来扩展其处理能力。

设计时注意要点

以下是此次设计时的两个注意要点:

  1. 断点续传支持:静态资源服务器通常支持断点续传。当用户下载大文件时,如果出现网络中断或其他原因导致下载中断,静态资源服务器可以记录中断位置,当用户重新请求下载时,可以恢复到中断的位置继续下载,提供更好的下载体验。

  2. 缓存和浏览器缓存支持:静态资源服务器可以通过设置合适的缓存策略,利用浏览器缓存来提高性能和减少网络流量。静态资源可以设置缓存过期时间,当浏览器再次请求相同资源时,可以直接从缓存中获取,减少了网络请求和传输时间。

启动文件服务器

对当前项目启动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的一个缺点是它基于服务器的时间,如果服务器的时间不准确,缓存可能会出问题。
  • Cache-Control:

    • Cache-Control是一个HTTP 1.1的头部字段,提供了更细粒度的缓存控制。
    • 它可以包含多个指令,如public、private、no-cache、max-age等,用于控制资源在浏览器缓存中的存储方式和有效期。
    • Cache-Control的优先级高于Expires,当两者同时存在时,Cache-Control的设置会覆盖Expires。

由于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_posend_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)的实现流程及相关的部分源码,希望可以让你更了解文件服务器内部的原理组成。

点击 [关注][在看][点赞] 是对作者最大的支持

标签:文件,缓存,HTTP,请求,45,Content,nginx,服务器,Rust
From: https://www.cnblogs.com/wmproxy/p/17988595/wmproxy45

相关文章

  • CF145E Lucky Queries 题解
    题目链接:CF或者洛谷前置知识点:序列操作本文关键词约定俗称:因为频繁敲最长不下降子序列\(LNCS\)和最长不上升子序列\(LNIS\)太麻烦了,下文将\(000011111\)这种最长不降子序列用\(LIS\)描述,\(1111100000\)这种最长不升子序列用\(LDS\)描述。这里面只有\(4\)和\(7......
  • nginx高级篇(二)
    一.基于ip地址的访问限制allowxxxx;denyxxxx;他俩的放置顺序;限制只允许10.0.0.0~10.0.0.255范围的IP访问限制只允许10.0.0.0~10.0.0.255范围的IP访问(禁止其他网段的访问)创建虚拟主机,完成该功能[root@web-8/etc/nginx/conf.d]#catdeny-allow.confserver{listen2266......
  • 无涯教程-Rust - 并发(Concurrency)
    在并发编程中,程序的不同部分独立执行,另一方面,在并行编程中,程序的不同部分会同时执行。线程数我们可以使用线程同时运行代码,在当前的操作系统中,已执行程序的代码在一个进程中运行,并且操作系统一次管理多个进程,在您的程序中,您还可以具有可以同时运行的独立部分,运行这些独立部分的......
  • Nginx日志检测分析工具 - WGCLOUD
    WGCLOUD可以对Nginx的日志文件进行全面分析,包括IP、sql注入、搜索引擎蜘蛛爬取记录、HTTP响应状态码、访问量最高的IP统计、扫描统计等效果如下图......
  • 无涯教程-Rust - 智能指针
    Rust默认情况下在堆栈上分配所有内容,您可以通过将它们包装在智能指针(如Box)中来将它们存储在堆上,智能指针实现下表中列出的特征-Sr.NoTraitnamePackage&描述1Derefstd::ops::Deref用于不可变的取消引用操作,如*v。2Dropstd::ops::Drop当值超出范围时用于......
  • nginx-rtmp-module 支持 Enhancing RTMP HEVC(H.265)
     EnhancingRTMP,FLV2023年7月31号正式发布,主要支持了HEVC(H.265)、VP9、AV1视频编码,发布差不多半年了,很多开源项目已支持,最近打算播放和推送端也支持下,想找个支持的rtmpserver方便测试用,但没找到合适的。干脆自己改改nginx-rtmp-module代码,做个基本的支持,能正常推送和播放En......
  • 无涯教程-Rust - 迭代&闭包
    在本章中,我们将学习RUST中的迭代器和闭包如何工作。Iterator迭代器迭代器有助于迭代值的集合,例如数组,向量,Map映射等,迭代器实现Rust标准库中定义的Iteratortrait,iter()方法返回集合的迭代器对象,迭代器对象中的值称为元素,迭代器的next()方法可用于遍历元素,当到达集合末尾时,next......
  • 无涯教程-Rust - 文件输入&输出
    除了对控制台进行读写之外,Rust还允许对文件进行读写,File结构代表一个文件,它允许程序对文件执行读写操作,File结构中的所有方法均返回io::Result枚举的变体。写入文件以下程序创建文件"data.txt",create()方法用于创建文件,如果文件创建成功,该方法将返回文件句柄,最后一行write_a......
  • 洛谷题单指南-模拟和高精度-P1045 [NOIP2003 普及组] 麦森数
    原题链接:https://www.luogu.com.cn/problem/P1045题意解读:要计算2p-1的位数和最后500位,实际上只需要计算2p,两者位数一致,前者比后者个位减1即可,且个位肯定不会是0,比较容易处理。解题思路:如果直接采用高精度乘法计算2p,p最大3.1*106,高精度所用数组最长大概9*105,一共最多计算3.......
  • 无涯教程-Rust - 输入&输出
    本章讨论如何接受来自标准输入Input的值以及如何将值显示到标准输出Output,在本章中,我们还将讨论传递命令行参数。读和写Rust的input和output标准库函数围绕两个特征进行组织-Read读Write写Sr.NoTrait&描述Example1Read- 实现Read的类型具有面向字节输入的方法。S......