Separate dev deployment, main validation and GitHub Git mirroring #1
No files matched your search
@@ -160,6 +160,7 @@ The Bearer authentication upgrade requires the frontend and backend to be releas
|
||||
- Resetting settings retains the account, resources, plugin storage, and local tabs.
|
||||
- Logs live in `logs/`, with three rotated files retained, each up to 10 MiB. Settings can export logs and a version summary; this does not reconstruct historical logs.
|
||||
- If the interface breaks after an update, first check that the frontend and backend come from the same build, then inspect browser network requests and application logs. See the [user guide](guide.md#plugins-and-interface-recovery) for plugin and appearance recovery entry points.
|
||||
- Note reads send `Cache-Control: no-store, no-transform` so proxies preserve the strong revision ETag used by conditional saves. A proxy rewriting it to a weak ETag can cause otherwise valid edits to return HTTP 412; preserve this response policy through the public ingress.
|
||||
|
||||
## Managed development and production
|
||||
|
||||
|
||||
@@ -160,6 +160,7 @@ Bearer 认证升级需要前后端一起发布。旧 Cookie 页面不能继续
|
||||
- 重置设置保留账户、资源、插件存储和本机标签页。
|
||||
- 日志位于 `logs/`,轮转保留三份,每份至多 10 MiB;可在设置中导出日志及版本摘要。该功能不补录历史日志。
|
||||
- 更新后界面异常先核对前后端是否来自同一构建,再查看浏览器网络请求和应用日志;插件与外观恢复入口见[使用指南](guide.zh-CN.md#插件与界面恢复)。
|
||||
- 笔记读取返回 `Cache-Control: no-store, no-transform`,防止代理改写条件保存使用的强版本 ETag。代理将其改为弱 ETag 可能导致正常编辑返回 HTTP 412;公网入口须保留该响应策略。
|
||||
|
||||
## 开发与生产托管
|
||||
|
||||
|
||||
+2
-1
@@ -327,7 +327,8 @@ func (s *Server) readFile(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/markdown; charset=utf-8")
|
||||
w.Header().Set("ETag", `"`+rev+`"`)
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
// Preserve the strong revision ETag through compression proxies for If-Match writes.
|
||||
w.Header().Set("Cache-Control", "no-store, no-transform")
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,23 @@ type fixture struct {
|
||||
admin *http.Client
|
||||
}
|
||||
|
||||
func TestNoteRevisionSurvivesProxyBoundary(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
f.write("proxy.md", "original")
|
||||
res := f.do(f.admin, "GET", "/api/v1/files/proxy.md", "", nil)
|
||||
defer res.Body.Close()
|
||||
etag := res.Header.Get("ETag")
|
||||
if res.StatusCode != http.StatusOK || res.Header.Get("Cache-Control") != "no-store, no-transform" || !strings.HasPrefix(etag, `"`) {
|
||||
t.Fatalf("笔记响应必须保留强版本标识并禁止代理转换: %d %v", res.StatusCode, res.Header)
|
||||
}
|
||||
if code := f.status(f.admin, "PUT", "/api/v1/files/proxy.md", "updated", map[string]string{"If-Match": etag}); code != http.StatusOK {
|
||||
t.Fatalf("读取的版本标识应允许条件保存: %d", code)
|
||||
}
|
||||
if code := f.status(f.admin, "PUT", "/api/v1/files/proxy.md", "stale", map[string]string{"If-Match": etag}); code != http.StatusPreconditionFailed {
|
||||
t.Fatalf("过期版本必须仍拒绝保存: %d", code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMoveBroadcastsWhenAssetRewriteReplacesFileIdentity(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
f.write("from.md", "")
|
||||
@@ -274,7 +291,11 @@ func TestAPIResponsesAreNotCached(t *testing.T) {
|
||||
// Firefox once cached identity and tree responses offline, incorrectly opening an unloadable workspace.
|
||||
for _, path := range []string{"/api/v1/auth/state", "/api/v1/tree", "/api/v1/files/笔记.md", "/api/v1/raw/笔记.md"} {
|
||||
res := f.do(f.admin, "GET", path, "", nil)
|
||||
if res.StatusCode != http.StatusOK || res.Header.Get("Cache-Control") != "no-store" {
|
||||
want := "no-store"
|
||||
if strings.HasPrefix(path, "/api/v1/files/") {
|
||||
want = "no-store, no-transform"
|
||||
}
|
||||
if res.StatusCode != http.StatusOK || res.Header.Get("Cache-Control") != want {
|
||||
t.Errorf("%s:状态 %d,缓存策略 %q", path, res.StatusCode, res.Header.Get("Cache-Control"))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user