feat(server): add Go backend with vault, auth, events and REST API
- vault: path validation (no traversal, hidden segments or symlink escape), atomic writes with If-Match/If-None-Match revisions, moves that carry the note's .assets folder and rewrite its image links, trash with restore, content-addressed asset uploads - auth: single-user Argon2id password, sliding sessions in SQLite, login rate limiting, first-run setup gated by a one-time code written to the data directory - watch: fsnotify plus periodic rescans diffed against a snapshot, so both RawMark and external edits are broadcast as tree/changed/moved/ deleted events over WebSocket - api: stdlib router, SameSite=Strict cookies plus Origin checks, sandboxed raw file serving, embedded SPA with gzip and CSP
This commit is contained in:
1 parent
5746f2e9ae
commit
c4d2bd4636
15 files changed
+2196
No files matched your search
@@ -0,0 +1,53 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/coder/websocket"
|
||||
)
|
||||
|
||||
// events 通过 WebSocket 推送仓库变更。websocket.Accept 默认校验 Origin 与 Host 一致
|
||||
func (s *Server) events(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := websocket.Accept(w, r, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn.CloseNow()
|
||||
// 客户端不发消息;CloseRead 负责处理控制帧并在连接断开时取消 ctx
|
||||
ctx := conn.CloseRead(r.Context())
|
||||
msgs, unsubscribe := s.hub.Subscribe()
|
||||
defer unsubscribe()
|
||||
// 定期 ping,避免反向代理因空闲断开连接
|
||||
ping := time.NewTicker(30 * time.Second)
|
||||
defer ping.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case msg, ok := <-msgs:
|
||||
if !ok {
|
||||
// 客户端消费过慢被移出广播列表,断开后由客户端重连并全量同步
|
||||
conn.Close(websocket.StatusTryAgainLater, "too slow")
|
||||
return
|
||||
}
|
||||
if err := writeWithTimeout(ctx, conn, msg); err != nil {
|
||||
return
|
||||
}
|
||||
case <-ping.C:
|
||||
pctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
err := conn.Ping(pctx)
|
||||
cancel()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeWithTimeout(ctx context.Context, conn *websocket.Conn, msg []byte) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
return conn.Write(ctx, websocket.MessageText, msg)
|
||||
}
|
||||
Reference in new issue
Block a user