FastAPI/Starlette支持静态文件支持SPA
Posted | stdout
FastAPI 官方支持 from fastapi.staticfiles import StaticFiles
充当一个静态文件服务器
其实实现是 starlette。这玩意可以在 directory
下放一个 404.html
,恰好单页应用也需要用 index.html
充当所有 javascript 框架注册的 router
只是有一个毛病,这货返回的 HTTP status code 是 404。用起来没啥大毛病,但是就是浏览器不会记录网址,导致没法匹配浏览历史快速找到之前访问过的页面。
拿来改改继续用
from starlette.staticfiles import StaticFiles, Scope, Headers, Response, FileResponse
class StaticFilesWithout404(StaticFiles):
async def get_response(self, path: str, scope: Scope) -> Response:
r = await super().get_response(path, scope)
h = Headers(scope=scope)
full_path, stat_result = await self.lookup_path('404.html')
if isinstance(r, FileResponse) and r.path == full_path:
if 'text/html' in (h.get('accept') or ''):
r.status_code = 200
else:
return Response('', status_code=404)
return r
app.mount("/frontend", StaticFilesWithout404(directory="frontend", html=True), name="static")
这段代码大概是起到了类似 nginx try_files
的作用。默认静态文件映射一个目录,但是如果找不到就按根目录的 index.html 输出。这里还判断了请求的 accept
头,如果是 xhr/Fetch 就会直接返回一个0字节长度无MIME的404。
由此我想到了一个学究式的 leaky abstraction。众所周知,如果一个URL不存在,那么服务器应该返回404
但是现在都是 SPA 单页应用,都是先 200 返回 index.html
的内容,再由 javascript 的 router 去决定是否正常显示还是404 。所以返回 200 但是显示 404 就破坏了这个语义。如果要精确匹配SPA里的router如果不存在由服务器返回404,第一是搞 SSR,把前端那一坨代码跑在服务器端,第二种办法是 npm build 的时候输出一个可路由URL列表,然后部署的时候导入静态文件服务器更新。这个具体实现就留作homework由读者自行解决了。
Comments