使用 Docker 和 Nginx 實現簡單目錄索引服務

本文將會介紹如何使用 Docker、Node、JavaScript、Traefik 完成一個簡單的目錄索引服務,全部代碼在 300 行以內。相關代碼已開源至 GitHub ,文末有鏈接,感興趣可以自取。

實現一個目錄索引站點並不是什麼難事,但是即便如此,需要考慮的事情也有很多,要實現非阻塞IO、要實現文檔緩存、要實現SSL等等一系列稍微有些麻煩的事情,如何能在儘可能少編寫代碼的情況下,完成這個需求呢。

其實很簡單,藉助完善靠譜的開源項目們,本文最終實現例子效果如下。

使用 Docker 和 Nginx 實現簡單目錄索引服務
使用 Docker 和 Nginx 實現簡單目錄索引服務

實現核心邏輯

説到 Web 目錄索引服務,我們一般會想到的就是大名鼎鼎的 Nginx 或者它的競品們了。而它其中一個默認模塊便提供了這個目錄列表的功能: ngx_http_autoindex_module

這個模塊十分簡單,在此我就不過度展開,有興趣可以翻閲 Nginx 官方文檔,瞭解這個模塊提供的幾個簡單的指令。

對某個路由下的頁面開啟 autoIndex 可以輕鬆實現列目錄的功能,比如這樣:

location / {
    autoindex_format        html;
    autoindex_localtime     on;
    autoindex_exact_size    on;
    autoindex               on;
}

如果你簡單使用上面的邏輯,你會得到一個黑底白字的頁面,雖然能用,但是未免太過醜陋,查看生成文檔源代碼(由於代碼高亮問題,使用 xpre 代替 pre ):

<html>
<head><title>Index of /</title></head>
<body>
<h1>Index of /</h1><hr><xpre><a href="../">../</a>
<a href="a/">a/</a>                                                 16-Dec-2018 13:39                   -
<a href="b/">b/</a>                                                 16-Dec-2018 13:39                   -
<a href="c/">c/</a>                                                 16-Dec-2018 13:39                   -
</xpre><hr></body>
</html>

這個時候一般會有兩個方案對默認的界面進行美化:

ngx_http_addition_module

第一種方案需要額外編譯,有一定的額外維護成本、以及後續升級改造的不穩定因素。我們選擇第二種方式,比如將上面的邏輯改造為:

location / {
    add_before_body         /autoindex/header.html;
    add_after_body          /autoindex/footer.html;
 
    autoindex_format        html;
    autoindex_localtime     on;
    autoindex_exact_size    on;
    autoindex               on;
}

代碼生效後,你將得到這樣的文檔結果:

<!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>
<h1>Index of /</h1><hr><xpre><a href="../">../</a>
<a href="a/">a/</a>                                                 16-Dec-2018 13:39                   -
<a href="b/">b/</a>                                                 16-Dec-2018 13:39                   -
<a 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 href="https://soulteary.com" target="_blank">Proudly Powered By Nginx, Design By @soulteary</a></th></tr></tfoot></table>
<script>your code here</script>
</body>
</html>

這時你會發現樣式似乎是正常了,但是會出現三個額外的問題:

  1. 文檔閉合不標準,存在多個文檔閉合標籤。
  2. Nginx AutoIndex 默認生成的 HTML 文檔存在內聯樣式標籤,無法像三方模塊一樣進行頁面定製。
  3. 默認生成文檔結構不利於SEO以及不利於頁面樣式定製。

但是很慶幸,Nginx 還提供了一個內置模塊: ngx_http_sub_module 。 這個模塊擁有編程語言中 replace 函數的作用,配合少量的替換操作,我們可以將文檔輕鬆改造成我們想要的結構。

location / {
    add_before_body         /autoindex/header.html;
    add_after_body          /autoindex/footer.html;
 
    autoindex_format        html;
    autoindex_localtime     on;
    autoindex_exact_size    on;
    autoindex               on;
 
    sub_filter          '<html>\r\n<head><title>Index of $uri</title></head>' '';
    sub_filter          '<body bgcolor="white">' '';
    sub_filter          '</body>' '';
    sub_filter          '<hr>' '';
    sub_filter          '<hr>' '';
    sub_filter          '</html>' '';
    sub_filter_once     on;
 
    charset             utf-8;
}

此刻,再查看文檔,會發現文檔已經十分適合進行樣式改造了。

<!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>
<body>
<h1>Index of /</h1><xpre><a href="../">../</a>
<a href="a/">a/</a>                                                 16-Dec-2018 13:39                   -
<a href="b/">b/</a>                                                 16-Dec-2018 13:39                   -
<a href="c/">c/</a>                                                 16-Dec-2018 13:39                   -
</xpre>
 
<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 href="https://soulteary.com" target="_blank">Proudly Powered By Nginx, Design By @soulteary</a></th></tr></tfoot></table>
<script>your code here</script>
</body>
</html>

在有一個良好的文檔基礎之後,我們可以使用 JavaScript 對它進行簡單的增強,考慮到最基礎瀏覽器的兼容問題,我們使用 ES5 標準進行邏輯書寫,下面不到二十行的代碼,可以讓我們使用文檔中的 pre 標籤作為數據源,重新生成適合排版的模板。

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 href="..">..</a></td></tr>' + tpl;
document.getElementsByTagName('tbody')[0].innerHTML = tpl;

當然,如果你想擁有更適合閲讀的時間戳,可以引入一個名為 timeago.js 的腳本,配合下面的代碼,讓 Nginx 輸出的時間戳變可讀性變的更好。

timeago().render(document.querySelectorAll('.date'));

藉助容器快速服務化

因為我們並未對 Nginx 進行任何改造,所以我們可以很省事的直接使用 Nginx 官方鏡像提供我們的目錄索引服務,這裏推薦使用 alpine 鏡像,小巧好用,比如下面的鏡像,連帶系統到軟件,不到 20 MB

nginx:1.15.7-alpine

為了簡單,我直接使用 composeTraefik 完成搭建應用的最後一步,相關的説明之前的博客有寫,我就不贅述了,還是不太會使用的同學請翻閲歷史文檔。

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"
 
networks:
  traefik:
    external: true

當然,既然提到服務,最低要求是能夠自動負載均衡、並且提供多節點存活,比如下面這樣。

docker-compose up --scale nginx=2

最後

可能你會覺得這麼一頓折騰,相比 Nginx 默認配置性能會有很大降低,然而事實是並沒有,有興趣的同學可以進行性能壓測。

先寫到這裏。

—EOF