本文使用「署名 4.0 國際 (CC BY 4.0)」許可協定,歡迎轉載、或重新修改使用,但需要注明來源。 署名 4.0 國際 (CC BY 4.0)
本文作者: 蘇洋
建立時間: 2018年12月16日 統計字數: 3548字 閱讀時間: 8分鐘閱讀 本文連結: https://soulteary.com/2018/12/16/implement-a-simple-directory-indexing-service-using-docker-and-nginx.html
本文将會介紹如何使用 Docker、Node、JavaScript、Traefik 完成一個簡單的目錄索引服務,全部代碼在 300 行以内。相關代碼已開源至 GitHub ,文末有連結,感興趣可以自取。
實作一個目錄索引站點并不是什麼難事,但是即便如此,需要考慮的事情也有很多,要實作非阻塞IO、要實作檔案緩存、要實作SSL等等一系列稍微有些麻煩的事情,如何能在盡可能少編寫代碼的情況下,完成這個需求呢。
其實很簡單,借助完善靠譜的開源項目們,本文最終實作例子效果如下。

實作核心邏輯
說到 Web 目錄索引服務,我們一般會想到的就是大名鼎鼎的 Nginx 或者它的競品們了。而它其中一個預設子產品便提供了這個目錄清單的功能: ngx_http_autoindex_module。
這個子產品十分簡單,在此我就不過度展開,有興趣可以翻閱 Nginx 官方文檔,了解這個子產品提供的幾個簡單的指令。
對某個路由下的頁面開啟 autoIndex 可以輕松實作列目錄的功能,比如這樣:
如果你簡單使用上面的邏輯,你會得到一個黑底白字的頁面,雖然能用,但是未免太過醜陋,檢視生成文檔源代碼(由于代碼高亮問題,使用 xpre 代替 pre):
這個時候一般會有兩個方案對預設的界面進行美化:
編譯一個支援定義模闆的 Nginx 插件。
使用 ngx_http_addition_module 子產品手動進行模闆美化。
第一種方案需要額外編譯,有一定的額外維護成本、以及後續更新改造的不穩定因素。我們選擇第二種方式,比如将上面的邏輯改造為:
location / {
add_before_body /autoindex/header.html;
add_after_body /autoindex/footer.html;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>小站</title>
<style>your code here</style>
</head>
<body><html>
<head><title>Index of /</title></head>
<body>
Index of /<hr><xpre><a rel="nofollow" href="../">../</a>
<a rel="nofollow" href="a/">a/</a> 16-Dec-2018 13:39 -
<a rel="nofollow" href="b/">b/</a> 16-Dec-2018 13:39 -
<a rel="nofollow" href="c/">c/</a> 16-Dec-2018 13:39 -
</xpre><hr></body>
</html>
<table><thead><tr><th width="40%">Name</th><th width="30%">Date</th><th width="10%">Size</th></tr></thead><tbody></tbody><tfoot><tr><th colspan="3"><a rel="nofollow" href="https://soulteary.com" target="_blank">Proudly Powered By Nginx, Design By @soulteary</a></th></tr></tfoot></table>
<script>your code here</script>
</body>
Index of /<xpre><a rel="nofollow" href="../">../</a>
</xpre>
var dataSets = document.getElementsByTagName('pre')[0].innerHTML.split('\n');
var directoryUp = false;
var tpl = '';
for (var i = 0, j = dataSets.length - 1; i < j; i++) {
var line = dataSets[i];
if (line.indexOf('../') === -1) {
line = line.match(/^(.*)\s+(\S+\s\S+)\s+(\S+)/);
tpl += '<tr>' +
'<td>' + line[1] + '</td>' +
'<td>' + '<span class="date" datetime="' + new Date(line[2]) + '">' + line[2] + '</span>' + '</td>' +
'<td>' + line[3] + '</td>' +
'</tr>';
} else {
if (location.pathname !== '/') directoryUp = true;
if (directoryUp) tpl = '<tr><td colspan="3"><a rel="nofollow" href="..">..</a></td></tr>' + tpl;
document.getElementsByTagName('tbody')[0].innerHTML = tpl;
timeago().render(document.querySelectorAll('.date'));
nginx:1.15.7-alpine
version: '3'
services:
nginx:
image: nginx:1.15.7-alpine
restart: always
labels:
"traefik.enable=true"
"traefik.port=80"
"traefik.frontend.rule=Host:demo.soulteary.com,demo.soulteary.io"
"traefik.frontend.entryPoints=https,http"
"traefik.frontend.headers.customResponseHeaders=Access-Control-Allow-Origin:*"
networks:
traefik
expose:
80
volumes:
./nginx.conf:/etc/nginx/nginx.conf
./mime.types:/etc/nginx/mime.types
./public:/app/public
./autoindex:/app/.autoindex
extra_hosts:
"demo.soulteary.com:127.0.0.1"
"demo.soulteary.io:127.0.0.1"
traefik:
external: true
docker-compose up --scale nginx=2