使用 Docker 和 Nginx 實現簡單目錄索引服務
本文將會介紹如何使用 Docker、Node、JavaScript/">JavaScript、Traefik 完成一個簡單的目錄索引服務,全部程式碼在 300 行以內。相關程式碼已開源至 GitHub/">GitHub ,文末有連結,感興趣可以自取。
實現一個目錄索引站點並不是什麼難事,但是即便如此,需要考慮的事情也有很多,要實現非阻塞IO、要實現檔案快取、要實現SSL等等一系列稍微有些麻煩的事情,如何能在儘可能少編寫程式碼的情況下,完成這個需求呢。
其實很簡單,藉助完善靠譜的開源專案們,本文最終實現例子效果如下。
實現核心邏輯
說到 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>
這時你會發現樣式似乎是正常了,但是會出現三個額外的問題:
- 文件閉合不標準,存在多個文件閉合標籤。
- Nginx AutoIndex 預設生成的 HTML 文件存在內聯樣式標籤,無法像三方模組一樣進行頁面定製。
- 預設生成文件結構不利於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
為了簡單,我直接使用 compose
和 Traefik
完成搭建應用的最後一步,相關的說明之前的部落格有寫,我就不贅述了,還是不太會使用的同學請翻閱歷史文件。
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 預設配置效能會有很大降低,然而事實是並沒有,有興趣的同學可以進行效能壓測。
- 原始碼地址: ofollow,noindex" target="_blank">https://github.com/soulteary/autoindex
先寫到這裡。
—EOF