如何用烧瓶服务静态网站

How to serve static site with flask

我使用 MKdocs 创建一个非常敏感的文档,​​我希望它可以安全地在线访问。
我的项目是:

(这样服务器上就没有持久化的明文数据了)

文档仅在本地编辑、加密并同步到服务器。

现在,我在 flask 步骤的服务站点上被阻止了,因为 MKdocs 站点的结构如下:

├── 404.html
├── documentation
│   ├── Folder 1
│   │   ├── Sub folder 1
│   │   │   └── index.html
│   │   └── Sub Folder 2
│   │       └── index.html
│   ├── Folder 2
├── css
│   └── ...
├── fonts
│   └── ...
├── img
│   └── ...
├── index.html
├── js
│   └── ...
├── search
│   └── ...
└── ...

我试图将此内容放在 ./static/site/ 下,然后用 flask

替换所有本地 hrefsrc 链接
{{ url_for('static', filename='site/[original_content]) }}

但据我所知,它无法在像

这样的静态调用上工作
app.send_static_file('site/index.html')

因为没有替换{{ .. }}的内容(而且我预料到了这个问题,是静态页面)

问题: 有没有一种方法可以使用 flask 为具有这种“复杂”结构的静态站点提供服务?

MORE and not necessary information, spoiler is not part of question!:

就我能接受的关于如何以不同方式做整件事的任何建议而言,现在,我想实现它。
我想到的其他解决方案:
- 与 nginx 一起服务静态网站,在一个空文件夹上,使用以前的机制的 flask 将解密该卷。不知道如何检测nginx会话结束再加密
- 将所有结构压缩在一个大 HTML 文件中。这将是 很棒的 但我确实浪费了太多时间进行搜索和尝试,但是......我的研究没有运气。 (但这是我推荐的解决方案)

我们可以将 MkDocs 呈现的 HTML 页面视为简单的 Flask 模板。所以我们所要做的就是创建一个 Flask 端点,它首先执行 permissions-checking 然后基于 URL,服务于呈现的 MkDocs HTML 文件或相关的静态文件。

让我们称我们的新端点为 bridge。首先,将 MkDocs site 目录中的所有内容放到 Flask 的 templates/bridge 目录中。我的示例 MkDocs 树如下所示:

$ tree templates/bridge
templates/bridge
├── 404.html
├── css
│   ├── base.css
│   ├── bootstrap.min.css
│   └── font-awesome.min.css
├── dir1
│   ├── sub1
│   │   └── index.html
│   └── sub2
│       └── index.html
├── dir2
│   ├── sub1
│   │   └── index.html
│   └── sub2
│       └── index.html
├── fonts
│   ├── fontawesome-webfont.eot
│   ├── fontawesome-webfont.svg
│   ├── fontawesome-webfont.ttf
│   ├── fontawesome-webfont.woff
│   └── fontawesome-webfont.woff2
├── img
│   ├── favicon.ico
│   └── grid.png
├── index.html
├── js
│   ├── base.js
│   ├── bootstrap.min.js
│   └── jquery-1.10.2.min.js
├── search
│   ├── lunr.js
│   ├── main.js
│   ├── search_index.json
│   └── worker.js
├── sitemap.xml
└── sitemap.xml.gz

我们的新 Flask bridge 端点:

from flask import Flask, render_template, send_from_directory

app = Flask(__name__)

@app.route('/bridge/')
@app.route('/bridge/<path:p1>/')
@app.route('/bridge/<path:p1>/<path:p2>/')
@app.route('/bridge/<path:p1>/<path:p2>/<path:p3>/')
def bridge(p1=None, p2=None, p3=None):
    # Permissions checking...

    # Serve MkDocs's static files requested from CSS files
    if p1 == 'css' and p2 in ('img', 'fonts'):
        # CSS fix, e.g. /bridge/css/img/example.png -> /bridge/img/example.png
        return send_from_directory(f'templates/bridge/{p2}/', p3)

    # Serve MkDocs's static files
    if p1 in ('css', 'js', 'fonts', 'search'):
        return send_from_directory(f'templates/bridge/{p1}/', p2)

    # Serve rendered MkDocs HTML files
    if p3 != None:
        template = f'bridge/{p1}/{p2}/{p3}/index.html'
    elif p2 != None:
        template = f'bridge/{p1}/{p2}/index.html'
    elif p1 != None:
        template = f'bridge/{p1}/index.html'
    else:
        template = 'bridge/index.html'

    return render_template(template)

如您所见,我们创建了几个路径定义。由于 MkDocs 到处都使用相对路径,MkDocs 中的 URL 路径 dir1/sub1/ 将变为 https://yoursite.com/bridge/dir1/sub1/ 因此我们可以使用此 URL 路由方案和 URL 部分捕获它们将进入 p1p2p3 路径变量,我们将使用这些变量来提供相应的内容。

有两种类型的内容:静态文件(例如 CSS 文件或图像)和 HTML 内容文件。静态文件将位于 cssjsimagesfonts 等目录中,因此当 p1 等于其中之一时,我们使用 Flask 的 send_from_directory函数来为他们服务。从 CSS 文件引用的静态文件需要进行特定处理,因为 MkDocs 也在那里使用相对路径。

而对于呈现的index.html文件,我们只需要根据路径和return选择的index.html文件确定嵌套级别作为普通Flask模板。由于 MkDocs 使用相对 URLs 我们不需要修改呈现的 HTML 文件中的任何内容,每个 URL 都会收到 /bridge/ 前缀,因此我们可以为它们提供服务我们的 bridge 端点。

您应该在 bridge 的开头进行权限检查(或作为装饰器,具体取决于您的解决方案)。如果你有更深的嵌套内容,你可能需要添加 p4 and/or p5 路径变量,但它是我示例的直接扩展。

注意:404 错误页面也将由 Flask 提供。

我对你的代码有很多问题,我在解密后重定向到 /bridge/index.html,从那里点击的任何内容都会以 /bridge/index.html/resource/p1/p2/etc..

开头

之后我不得不重新考虑 p1、p2 和其他人的位置,我发现完全基于 Dauros 解决方案的解决方案,我真正喜欢的是:

@app.route('/<path:p1>/')
@app.route('/<path:p1>/<path:p2>/')
@app.route('/<path:p1>/<path:p2>/<path:p3>/')
@app.route('/<path:p1>/<path:p2>/<path:p3>/<path:p4>/')
@app.route('/<path:p1>/<path:p2>/<path:p3>/<path:p4>/<path:p5>/')
@app.route('/<path:p1>/<path:p2>/<path:p3>/<path:p4>/<path:p5>/<path:p6>/')
def bridge(p1=None, p2=None, p3=None, p4=None, p5=None, p6=None):
    # Permissions checking...
    # I'm planning on using basic auth from nginx to even try to show a page
    
    resource = '/'.join([p for p in (p1,p2,p3,p4,p5,p6) if p])
        
    the_file = resource.split('/')[-1]
    the_path = '/'.join(resource.split('/')[:-1])
    
    if p1 in ('css', 'fonts', 'img', 'js', 'search'):
        return send_from_directory(f'templates/bridge/{the_path}/', the_file)

    else:
        template = f'bridge/{resource}/index.html'
        return render_template(template)


@app.route('/', methods=HTTP_METHODS)
def home():
    method = request.method
    if method == "GET":
        # TO BE CHANGED, checking if volume is already decrypted.
        if os.path.exists(f"{BASE_FODLER}/locked"):
            # decrypt page
            resp = make_response(render_template('decrypt.html'))
            return resp
        else:
            template = 'bridge/index.html'
            return render_template(template)
    elif method == "POST":
        if 'password' in request.form:
            decrypt_key = request.form['password']
            # decryption omitted
        
            template = 'bridge/index.html'
            return render_template(template)
        else:
            return not_allowed_ans()
        
    else:
        return not_allowed_ans()
    
    return SendOk()

如您所见,从底部开始,template = 'bridge/index.html' 然后 return render_template(template) 将网桥站点置于根目录 mystime.com/ 这意味着所有路由都必须是根目录。
任何“link”将是:mysite.com/folder_1/sub_folder_1/index.html
之后我让我在未来添加 pX 变得简单(使用动态 PX 的单一路由会很好,但是,这对于静态文档来说已经足够了!)