GoaのWebSocket統合により、サービスでリアルタイムの双方向通信を処理できるようになります。 このガイドでは、基本的な概念から高度な実装まで、サービスでWebSocket接続を実装する方法を説明します。
WebSocketは、単一のTCP接続上で全二重通信を提供するプロトコルです。Goaは、ストリーミングDSLを通じて WebSocketサポートを実装し、3つの主要なパターンを提供します:
StreamingPayload
):クライアントがメッセージのストリームをサーバーに送信StreamingResult
):サーバーがメッセージのストリームをクライアントに送信WebSocket接続は常に、プロトコルアップグレードのためのGETリクエストで開始されます。Goaでは、これは以下を意味します:
// 論理的な操作に関係なく、すべてのWebSocketエンドポイントはGETを使用する必要があります
HTTP(func() {
GET("/stream") // WebSocketアップグレードに必要
Param("token") // 必要に応じて追加のパラメータ
})
チャットサービスの実装例を使用して、各ストリーミングパターンを見ていきましょう。
この例では、クライアントからメッセージを受信するリスナーを実装します:
Method("listener", func() {
// ストリームのメッセージフォーマット
StreamingPayload(func() {
Field(1, "message", String, "メッセージ内容")
Required("message")
})
HTTP(func() {
GET("/listen") // WebSocketエンドポイント
})
})
このデザインは:
この例では、クライアントに更新を送信するサブスクリプションサービスを作成します:
Method("subscribe", func() {
StreamingResult(func() {
Field(1, "message", String, "更新内容")
Field(2, "action", String, "アクションタイプ")
Field(3, "timestamp", String, "発生時刻")
Required("message", "action", "timestamp")
})
HTTP(func() {
GET("/subscribe")
})
})
このパターンは:
この例では、双方向通信を示すエコーサービスを作成します:
Method("echo", func() {
// クライアントメッセージ
StreamingPayload(func() {
Field(1, "message", String, "エコーするメッセージ")
Required("message")
})
// サーバーレスポンス
StreamingResult(func() {
Field(1, "message", String, "エコーされたメッセージ")
Required("message")
})
HTTP(func() {
GET("/echo")
})
})
このデザインは:
GoaでWebSocketサービスを実装する際は、サーバー側とクライアント側の両方のパターンを慎重に考慮する必要があります。 基本的な概念は単純ですが、適切な実装には接続管理、並行処理、エラー処理を考慮する必要があります。 チャットサービスを例にして、これらの側面を見ていきましょう。
WebSocketサービスのサーバー側は、メッセージを効率的に処理しながら、接続の完全なライフサイクルを管理する必要があります。 WebSocketサーバーの核心は、アクティブな接続を維持し、メッセージを並行して処理し、接続が終了したときに適切なクリーンアップを 確実に行うことです。
接続管理は、WebSocketサーバーの基礎を形成します。クライアントが接続すると、サーバーは接続を検証し、 必要な状態を設定し、メッセージ処理の準備をする必要があります。実際にはこのように見えます:
func (s *service) handleStream(ctx context.Context, stream Stream) error {
// 接続状態を初期化
connID := generateConnectionID()
s.registerConnection(connID, stream)
defer s.cleanupConnection(connID)
// メッセージ処理を開始
return s.processMessages(ctx, stream)
}
メッセージ処理では、並行性を慎重に扱う必要があります。サーバーはレスポンスを送信しながら同時にメッセージを 受信できる必要があります。これは通常、goroutineを使用してこれらの関心事を分離することで実現されます:
func (s *service) processMessages(ctx context.Context, stream Stream) error {
// 別のgoroutineで受信メッセージを処理
errChan := make(chan error, 1)
go func() {
errChan <- s.handleIncoming(stream)
}()
// コンテキストのキャンセルまたは処理エラーを待機
select {
case <-ctx.Done():
return ctx.Err()
case err := <-errChan:
return err
}
}
エラー処理は、WebSocket実装で特に重要です。接続はさまざまな方法で失敗する可能性があるためです。 ネットワークの問題、クライアントの切断、アプリケーションのエラーはすべて、サービスの安定性を維持するために 適切に処理される必要があります。
クライアント実装は独自の課題に直面します。堅牢なWebSocketクライアントは、接続を維持し、 両方向のメッセージフローを処理し、問題が発生しても良好なユーザーエクスペリエンスを提供する必要があります。
クライアント側の接続管理には、初期接続の確立と、失敗が発生したときの再接続の処理が含まれます。 自動再接続を実装するクライアントの例を示します:
func connectWithRetry(ctx context.Context) (*WSClient, error) {
for {
client, err := connect(ctx)
if err == nil {
return client, nil
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(backoffDuration):
// リトライループを継続
}
}
}
クライアントでのメッセージ処理では、ユーザー入力とサーバーメッセージの間の調整が必要になることがよくあります。 これは通常、適切な同期を確保しながら複数のgoroutineを管理することを含みます:
func (c *Client) handleMessages(ctx context.Context) {
// 受信メッセージを処理
go c.receiveMessages(ctx)
// ユーザー入力を処理
c.processUserInput(ctx)
}
WebSocketサービスを実装する際には、いくつかの課題が一般的に発生します。これらの課題とその解決策を理解することで、 より堅牢な実装を作成できます。
メッセージの順序は、リアルタイムアプリケーションで問題になる可能性があります。WebSocketは単一の接続内でメッセージの 順序を保証しますが、アプリケーションレベルでの順序付けが必要になる場合があります。例えば、チャットアプリケーションでは、 メッセージは送信された順序で表示される必要があります:
type Message struct {
Content string
Sequence int64
Timestamp time.Time
}
複数の接続やステートフルなプロトコルを扱う場合、状態管理は複雑になります。サービスは接続状態だけでなく、 アプリケーションの状態も追跡する必要があります。例えば、チャットルームサービスでは:
type ChatRoom struct {
ID string
Members map[string]*Connection
Messages []Message
LastActive time.Time
mu sync.RWMutex
}
リソース管理は、長期間存続する接続にとって重要です。接続が適切に追跡されクリーンアップされない場合、 メモリリークが発生する可能性があります。接続マネージャーはこれを処理するのに役立ちます:
type ConnectionManager struct {
active map[string]*Connection
mu sync.RWMutex
}
func (cm *ConnectionManager) cleanup() {
cm.mu.Lock()
defer cm.mu.Unlock()
for id, conn := range cm.active {
if !conn.isAlive() {
conn.close()
delete(cm.active, id)
}
}
}
WebSocketサービスでは、複雑な実世界の要件を処理するために高度な機能が必要になることがよくあります。 Goaが提供する、洗練されたWebSocketアプリケーションを構築するためのいくつかの強力な機能を見ていきましょう。