// remote_handle.go — RemoteHandle 是 ProxyClientStore.Lookup 回傳的 Handle 實作。 // // 它代表「session 在 remote-proxy 那邊」。OpenStream 走 Forwarder 的 raw forward。 // // 注意:RemoteHandle 不持有 yamux session(那在 remote-proxy 的記憶體裡), // 所以許多語意(Close / IsClosed)行為上跟 LocalHandle 不太一樣: // - Close 走 ProxyClient.CloseSession(HTTP 通知 remote-proxy 關閉) // - IsClosed 只能根據 Lookup 時的 Summary 判斷,無即時感知能力 // // 對 api-server handler 來說,這些差異是透明的 — 只要拿 handle.OpenStream // 來開 stream 就好。 package session import ( "context" "net" "sync" "sync/atomic" "time" ) // RemoteHandle 是 api-server 端的 Handle 實作。 type RemoteHandle struct { client ProxyClient forwarder *Forwarder mu sync.Mutex summary Summary // closed 用 atomic 避免每次 IsClosed 都 lock closed atomic.Bool } // newRemoteHandle 建立一個 RemoteHandle;package-internal,由 ProxyClientStore 用。 func newRemoteHandle(client ProxyClient, forwarder *Forwarder, sum *Summary) *RemoteHandle { h := &RemoteHandle{ client: client, forwarder: forwarder, } if sum != nil { h.summary = *sum } return h } // OpenStream 走 Forwarder 開一條 raw TCP(hijack)連線。 // // 若 Forwarder 為 nil(僅 metadata-only 場景)回明確錯誤。 func (h *RemoteHandle) OpenStream(ctx context.Context) (net.Conn, error) { if h.closed.Load() { return nil, ErrSessionClosed } if h.forwarder == nil { return nil, ErrNotSupported } return h.forwarder.OpenStream(ctx, h.summary.Token) } // Close 透過 ProxyClient 通知 remote-proxy 關閉這個 session。 // // 冪等:多次呼叫只會打一次 HTTP(後續直接回 nil)。 func (h *RemoteHandle) Close() error { if !h.closed.CompareAndSwap(false, true) { return nil // 已經 close 過 } if h.client == nil { return nil } // 用獨立的 ctx 避免 caller 取消後 close 半路斷掉 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() return h.client.CloseSession(ctx, h.summary.Token) } // IsClosed 回報是否已被本地 Close。 // // 注意:不會即時感知 remote-proxy 那邊的斷線; // 真正想確認 session 還在的話,應該再呼叫一次 Lookup。 func (h *RemoteHandle) IsClosed() bool { return h.closed.Load() } // Summary 回傳 session metadata 的 snapshot。 func (h *RemoteHandle) Summary() *Summary { h.mu.Lock() defer h.mu.Unlock() cp := h.summary return &cp } // RecordHeartbeat — RemoteHandle 不主動記錄心跳(心跳由 yamux 在 remote-proxy // 端維護);本方法純粹更新本地 summary 的 LastHeartbeat 欄位以維持 interface 契約。 func (h *RemoteHandle) RecordHeartbeat(t time.Time) { h.mu.Lock() defer h.mu.Unlock() h.summary.LastHeartbeat = t } // 編譯時檢查 var _ Handle = (*RemoteHandle)(nil)