jim800121chen adab000987 feat(camera): camera/media 推論改走 tunnel proxy(塊1,取代 501 stub)
推論工作區後端塊1:把 camera/media 9 條路徑從 501 stub 換成走
newProxyHandler 的 tunnel proxy 宣告(比照 devices.go 慣例,新增
camera.go 而非塞 stubs.go)。

- GET /camera/stream → streaming:true(MJPEG multipart 長連線逐 chunk flush)
- 其餘 8 條(camera list/start/stop、media upload×3、batch-images/:index、
  seek)→ request-response(upload 大檔的 request body 由 proxy 直接
  streaming 送出,streaming flag 只控 response)
- 掛在已套 AuthMiddleware 的 apiGroup 下,沿用剝 Origin 修正

WS inference:<deviceId> 結果推播為塊2(ForwardWebSocket);MJPEG <img>
的 stream 認證分支(R-C4)待 security 定案短期 stream-token 方案,本塊未做。

Reviewer 0C/0M/1Mi/3Sug 通過。+camera_test.go 路由黑箱測試(路徑不再
501、無 session 回 502 TUNNEL_DISCONNECTED),全套 api 回歸綠。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 04:36:06 +08:00

79 lines
3.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package api
import (
"github.com/gin-gonic/gin"
)
// registerStubRoutes 註冊 B5 尚未實作 / Phase 1 才處理的 endpoint一律回 501 NOT_IMPLEMENTED。
//
// **為什麼只留這些**Auth / Pairing 補齊 / Devices / Models / GET /clusters /
// system/deps / /storage 都在 B5 補實作(見 auth.go / devices.go / models.go /
// clusters.go / storage.go 各檔)。這裡只剩:
// - Cloud 裝置記錄(非 tunnel 的 CRUDPhase 1
// - Clusters 寫入類Phase 1
// - ConverterPhase 1
//
// Camera / Media 已在 camera.goregisterCameraRoutes換成真正的 tunnel proxy 宣告。
// - WebSocket endpointsB7 TODO — 需要 Hijack + WS relay
//
// 讓前端對錯誤路徑能拿到 501 而非 404減少除錯成本。
func registerStubRoutes(g *gin.RouterGroup, _ Deps) {
stub := func(hint string) gin.HandlerFunc {
return func(c *gin.Context) {
WriteNotImplemented(c, hint)
}
}
// --- Cloud-side device records非 tunnel ---
g.GET("/cloud/devices", stub("cloud.devices.list — pending Phase 1"))
g.POST("/cloud/devices/:id/rename", stub("cloud.devices.rename — pending Phase 1"))
// --- Models 其餘 ---
// load-to-device 已在 models.go 註冊為 501 stub為了讓 /api/models/:id/load-to-device 路徑註冊完整)。
// --- Clusters 寫入類 ---
g.POST("/clusters", stub("clusters.create — pending Phase 1"))
g.GET("/clusters/:id", stub("clusters.get — pending Phase 1"))
g.DELETE("/clusters/:id", stub("clusters.delete — pending Phase 1"))
g.POST("/clusters/:id/devices", stub("clusters.add-device — pending Phase 1"))
g.DELETE("/clusters/:id/devices/:deviceId", stub("clusters.remove-device — pending Phase 1"))
g.PUT("/clusters/:id/devices/:deviceId/weight", stub("clusters.set-weight — pending Phase 1"))
g.POST("/clusters/:id/flash", stub("clusters.flash — pending Phase 1"))
g.POST("/clusters/:id/inference/start", stub("clusters.inference.start — pending Phase 1"))
g.POST("/clusters/:id/inference/stop", stub("clusters.inference.stop — pending Phase 1"))
// --- Camera / Media走 tunnel proxy ---
// 已由 registerCameraRoutescamera.go換成真正的 proxy 宣告,不再是 501 stub。
// --- ConverterPhase 1 ---
g.POST("/converter/jobs", stub("converter.submit — pending Phase 1"))
g.GET("/converter/jobs", stub("converter.list — pending Phase 1"))
g.GET("/converter/jobs/:id", stub("converter.get — pending Phase 1"))
g.GET("/converter/jobs/:id/download", stub("converter.download — pending Phase 1"))
}
// registerWebSocketStubs 註冊 /ws/* 的 stub。WebSocket proxy 在 B5 雛形不實作,
// 留 501 讓前端能收到明確錯誤,由 B7 補齊。
//
// 為什麼不做 WS proxy實作 WS relay 需要在 api-server 端做 Hijack、雙向 io.Copy
// 而且 Forwarder.ForwardWebSocket 尚未實作(見 forwarder.go §ForwardWebSocket
// 加這條路徑會顯著擴張 B5 範圍;按 prompt 指示先留 TODO。
//
// 注意ws endpoint 在 /ws 而非 /api/ws所以由 NewRouter 直接註冊而非 apiGroup。
func registerWebSocketStubs(r *gin.Engine) {
stub := func(hint string) gin.HandlerFunc {
return func(c *gin.Context) {
WriteNotImplemented(c, hint)
}
}
// 用 GETWebSocket upgrade 的初始 HTTP request
r.GET("/ws/devices/events", stub("ws.devices.events — pending B7"))
r.GET("/ws/devices/:id/flash-progress", stub("ws.flash-progress — pending B7"))
r.GET("/ws/devices/:id/inference", stub("ws.inference — pending B7"))
r.GET("/ws/server-logs", stub("ws.server-logs — pending B7"))
r.GET("/ws/system", stub("ws.system — pending B7"))
r.GET("/ws/clusters/:id/inference", stub("ws.clusters.inference — pending B7"))
r.GET("/ws/clusters/:id/flash-progress", stub("ws.clusters.flash — pending B7"))
r.GET("/ws/pairing/status", stub("ws.pairing.status — pending B7"))
}