// proxy_store.go — ProxyClientStore 實作 Store interface(api-server 端使用)。 // // 雛形雙 binary 架構下: // - remote-proxy 持有 InMemoryStore,是唯一的 session state 來源 // - api-server 持有 ProxyClientStore,內部透過 ProxyClient 走 internal HTTP 查 remote-proxy // // 因為 api-server 是無狀態,所以「寫入類」操作(Register / Heartbeat / CleanupExpired) // 對 ProxyClientStore 都不適用 — 全部回 ErrNotSupported。 package session import ( "context" "errors" "time" ) // ProxyClientStore 是 Store 的 HTTP-client 實作,部署在 api-server 端。 // // 它把所有讀取類操作 delegate 到 ProxyClient(HTTP 呼叫 remote-proxy)。 // 寫入類操作(Register / Unregister / Heartbeat / CleanupExpired)一律回 // ErrNotSupported — 因為 session lifecycle 由 remote-proxy 唯一管理。 // // Lookup 回傳的 Handle 是 RemoteHandle(見下方),它的 OpenStream 會走 // `forwarder.go` 的 raw forward 流程。 type ProxyClientStore struct { client ProxyClient forwarder *Forwarder // 用於建立 RemoteHandle(OpenStream 時使用) } // NewProxyClientStore 建立一個 api-server 端的 SessionStore。 // // 入參: // - client:用於 metadata 操作(GetSession / ListSessions / CloseSession) // - forwarder:用於 RemoteHandle.OpenStream 走 raw forward // // forwarder 可為 nil(不需要 OpenStream,只要 metadata 查詢時);但實務上 // api-server 必定需要轉發,所以呼叫方應同時注入兩者。 func NewProxyClientStore(client ProxyClient, forwarder *Forwarder) *ProxyClientStore { return &ProxyClientStore{client: client, forwarder: forwarder} } // Register — ProxyClientStore 不支援;session 註冊由 remote-proxy 在 tunnel // upgrade 時完成。 func (s *ProxyClientStore) Register(ctx context.Context, token string, h Handle) error { return ErrNotSupported } // Unregister — 在 api-server 端等同於「強制關閉 session」,實際走 // CloseSession HTTP endpoint;不存在時為 no-op(對齊 InMemoryStore 行為)。 func (s *ProxyClientStore) Unregister(ctx context.Context, token string) error { if err := s.client.CloseSession(ctx, token); err != nil { // 不存在當作 no-op,與 InMemoryStore 一致 if errors.Is(err, ErrSessionNotFound) { return nil } return err } return nil } // Lookup 對應 ProxyClient.GetSession,回傳 RemoteHandle。 // // RemoteHandle 不持有 yamux session(它在 remote-proxy 那邊); // 它的 OpenStream 會透過 Forwarder 走 raw forward。 func (s *ProxyClientStore) Lookup(ctx context.Context, token string) (Handle, error) { sum, err := s.client.GetSession(ctx, token) if err != nil { return nil, err } return newRemoteHandle(s.client, s.forwarder, sum), nil } // Exists 透過 GetSession 判斷;不存在回 (false, nil),其他錯誤回 (false, err)。 func (s *ProxyClientStore) Exists(ctx context.Context, token string) (bool, error) { _, err := s.client.GetSession(ctx, token) if err != nil { if errors.Is(err, ErrSessionNotFound) { return false, nil } return false, err } return true, nil } // List 對應 ProxyClient.ListSessions。 func (s *ProxyClientStore) List(ctx context.Context) ([]*Summary, error) { return s.client.ListSessions(ctx) } // Heartbeat — ProxyClientStore 不支援;心跳由 yamux 的 keep-alive 自動維持, // 並由 remote-proxy 在實體 tunnel 上更新 LastHeartbeat。 func (s *ProxyClientStore) Heartbeat(ctx context.Context, token string) error { return ErrNotSupported } // CleanupExpired — ProxyClientStore 不支援;清理由 remote-proxy 的 // background goroutine 執行。 func (s *ProxyClientStore) CleanupExpired(ctx context.Context, expireAfter time.Duration) (int, error) { return 0, ErrNotSupported } // 編譯時檢查:確保 ProxyClientStore 實作 Store。 var _ Store = (*ProxyClientStore)(nil)