gite: https://gitee.com/tickbh/wmproxy
github: https://github.com/tickbh/wmproxy
HTTP檔案伺服器的意義是可以放置網站檔案,可以放置資料檔案。
HTTP伺服器一般指網站伺服器,是指駐留於因特網上某種型別計算機的程式,可以處理瀏覽器等Web使用者端的請求並返回相應響應。
當前大量的應用會依賴到檔案伺服器,比如我們非常熟悉的網站(會載入index.html)檔案及各種css及js檔案,比如我們的各種APP會有相對應的版本資訊,會有相應的版本檔案,又或者小程式本身就是一個可執行檔案,當你點選的時候,應用去下載相應的小程式檔案,然後在本地進行載入,然後開啟提供服務。目前我們的網際網路上衝浪完全無法離開檔案伺服器。
1. 檔案共用
檔案伺服器的主要功能是提供檔案共用功能。它允許使用者從他們自己的計算機或裝置存取共用檔案和資料夾,而不管他們的物理位置。使用者可以檢視、編輯和儲儲存存在伺服器上的檔案,所有有權存取該檔案的使用者都會自動更新更改。
以下是我們在聊天軟體上傳送一張圖片給另一個人的流程
2. 集中儲存
此時你的不小心將資料刪除,此時你想找回原來的圖片,以下是整個過程
此時檔案伺服器擔任著集中儲存的角色,海量的資料將匯聚在中心伺服器上,我們可以通過網路存取到海量的資料資源。
3. 備份與恢復
上述過程,相當於伺服器幫你備份了圖片資料,在你不小心丟失的時候,可以恢復您的資料,我們最經常使用的如圖片備份到網路硬碟,一方面可以釋放掉原生的空間,另一方面我們可以將資料儲存到很久之後。
4. 存取控制
我們在獲取到圖片地址的時候,並不是任何的角色都可以獲取到該圖片的資源,在伺服器內部中,會有相關的許可權驗證,在為您提供資料的同時,並保護著您的資料安全。
一個靜態檔案伺服器,支援真實和虛擬檔案系統。它通過將請求的URI路徑附加到站點的根路徑來形成檔案路徑。
最常見的是,file_server指令與root指令配對,為整個網站設定檔案根。其中保證所有的存取僅能在root指定的目錄之下,不能存取其上級的任何資料,故在root下的目錄理論上即使禁目錄存取也可能被全部存取到(暴力遍歷),但在root上級的目錄不可能被以任何的方式進行存取,即使新增../
相對路徑也不行。
結構定義如下:
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,
}
/file/
,那麼提供服務的將是/file/
的檔案服務/static/
,如果我們獲取到一個請求路徑如/static/src/wmproxy.md
,那麼我們會去掉字首得到src/wmproxy.md
,那麼實際的指向為/file/src/wmproxy.md
進行檔案服務index.html index.htm
。mimetype
,如README.md.gz
取的是md的mimetype
,也就是text/plain
。並適當地設定Content-Encoding響應頭。否則,將以正常的未壓縮檔案進行響應。如果encode指令被啟用,那麼如果沒有預壓縮,它可能會對響應進行即時壓縮。如我們存取README.md
,但此時目錄下存在README.md.gz
,那我們我們響應的是gz的檔案,並設定Content-Encoding: gzip
,如此做的好處,我們對該檔案的任何請求,我們都無須耗任何壓縮的時間,響應更快,我們可以用更高的壓縮比來進行預壓縮,可節省更多時間。404
。支援預留位置。預設情況下,寫入的狀態程式碼通常是200
,或206
,用於部分內容。reverse:
file_server:
reverse:
file_server:
browse: true
/static
資料夾中的靜態檔案:reverse:
file_server:
root: /static/
browse: true
reverse:
file_server:
root: /static/
browse: true
hide: [.git]
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值如下所示:
我們根據現有的已知的,我們用了靜態變數做了以下資料定義,後續將會進行資料補充或者自定義
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.rs
的deal_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);
}
}
如此靜態檔案伺服器則已初步實現,檔案服務中的壓縮及流式傳輸已基本完成