@ich0

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

React and ASP.NET Core プロジェクトでWebSocket が繋がらない [解決]

解決したいこと

Visual Studio 2022 Version 17.8 を利用してWebアプリを作っています。
Version 17.8 で新たに使えるようになった "React and ASP.NET Core" テンプレート (JavaScript用) を用いて新規作成したプロジェクトに対して、サーバ側・クライアント側でWebSocket通信のコード追加をしても、WebSocket接続がされない問題が発生しています。解決方法を教えてください。

なお、WebSocketの利用は下記のMicrosoft情報を参考にしています。

発生している問題

サーバー側に WebSocket の要求の受け入れ処理のコードを追加し、クライアント側に WebSocket の接続要求のコードを追加して実行するが、反応がない。

自分で試したこと (再現手順)

[手順1] Visual Studio 2022 (Version 17.8以降) を用いて "React and ASP.NET Core" プロジェクト (JavaScript) を新規作成します。
image.png
image.png
image.png

[手順2] サーバー側プロジェクトにある Program.cs に WebSocket接続要求を受け付けて、そのまま受信メッセージを送り返すコードを追加します。
image.png

//--- ここから追加
app.UseWebSockets();
app.Use(async (context, next) =>
{
    if (context.Request.Path == "/ws")
    {
        if (context.WebSockets.IsWebSocketRequest)
        {
            // WebSocket通信要求を受理する
            using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
            Console.WriteLine("WebSocket接続要求を受付");

            // メッセージを受け取る
            var buffer = new byte[1024 * 4];
            var receiveResult = await webSocket.ReceiveAsync(
                new ArraySegment<byte>(buffer), CancellationToken.None);

            // そのメッセージが通信クローズを意図したものではないならば...
            while (!receiveResult.CloseStatus.HasValue)
            {
                // そのメッセージをそのまま送り返す
                await webSocket.SendAsync(
                    new ArraySegment<byte>(buffer, 0, receiveResult.Count),
                    receiveResult.MessageType,
                    receiveResult.EndOfMessage,
                    CancellationToken.None);

                // 次のメッセージを受け取る
                receiveResult = await webSocket.ReceiveAsync(
                    new ArraySegment<byte>(buffer), CancellationToken.None);
            }

            // WebSocket通信をクローズする
            await webSocket.CloseAsync(
                receiveResult.CloseStatus.Value,
                receiveResult.CloseStatusDescription,
                CancellationToken.None);
        }
        else
        {
            context.Response.StatusCode = StatusCodes.Status400BadRequest;
        }
    }
    else
    {
        await next(context);
    }
});
//--- ここまで追加

[手順3] クライアント側プロジェクトにある ./src/App.jsx に WebSocket接続を要求し、簡単なメッセージ送受信するコードを追加します。
image.png

        //--- ここから追加
        var scheme = document.location.protocol === "https:" ? "wss" : "ws";
        var port = document.location.port ? (":" + document.location.port) : "";
        var url = scheme + "://" + document.location.hostname + port + "/ws";
        const socket = new WebSocket(url);
        console.log(`WebSocket connecting (URL: ${url}) ...`)
        socket.onopen = () => { console.log('WebSocket openned'); socket.send('Hello, world!'); }
        socket.onmessage = (ev) => console.log('WebSocket received: ' + ev.data);
        socket.onerror = () => console.log('WebSocket error');
        socket.onclose = (ev) => console.log(`WebSocket closed: ${ev.reason} (Code: ${ev.code})`);
        //--- ここまで追加

[手順4] Visual Studio 2022 でデバッグの開始 (F5 キーを押下) する。Vsiual Studio 2022 の「出力」ウィンドウに "WebSocket connecting..." と表示され、クライアント側で WebSocket オブジェクトが生成され、WebSocket 接続の要求をしているが、このまま何の変化もなく接続が成立しないままとなる。
そして4分間が経過したところで、エラーとなり接続 (接続要求) が Code 1006 で異常クローズする。
image.png

WebSoceket エラーコード を下記で参照したところ、Code 1006は「予約済み。ステータスコードが期待される接続が異常終了した(close frame が送信されない)ことを示す。」とある。おそらく、4分間経過してのタイムアウトエラーの意味と推測。

よろしくお願いします。

1 likes

2Answer

Comments

  1. @ich0

    Questioner

    実は組み込み系というか非常にH/Wリソース (ARM 2コア、RAM 1GB) の限られた環境でも動作させたいと構想しています。そのアプローチとしてネイティブAOTの活用を視野に入れていますが、下記の Microsoft 情報によると ASP.NET Core とネイティブ AOT の場合、SignalR はサポート外となっているため、生のWebSocketを使って実現したいというのが理由です。

  2. 参考にされている記事のサンプルコードでは期待通りになるのでしょうか?

    VS2022 のテンプレートで作る React + ASP.NET Core アプリは、フロントエンドからバックエンドへの通信はプロキシを使うようになるはずです。参考にされている記事のサンプルコードでは期待通りになるとすると、そこの違いが影響しているとか?

  3. @ich0

    Questioner

    参考にしたMicrosoft情報サイト (上記) に示されてるサンプルは下記の GitHub にコードがあります。これをダウンロードして実行すると WebSocket は正常に接続が行われて動作しています。ターゲットフレームワークを.NET 7.0 → .NET 8.0にしても正常に動作しています。

  4. ターゲットフレームワークを.NET 7.0 → .NET 8.0にしても正常に動作しています。

    自分はターゲットフレームワークのことは話してないです。プロキシの話をしています。

  5. @ich0

    Questioner

    そこの違いが影響しているとか?

    理解が追い付きませんでした。プロキシの使用の違いというのをもう少し詳しく教えて頂けないでしょうか。何か参考になる情報サイトでも構いません。よろしくお願いいたします。

  6. 最新のテンプレートで作る React + ASP.NET Core アプリは、React は Vite という開発サーバー、ASP.NET Core は Kestrel というサーバーでホストされるはずです。(注: 旧テンプレートでは異なります)

    Vite と Kestrel のドメイン名は localhost 部分は同じですがポート番号が異なります。Visual Studio でアプリを起動したとき、下のコマンド画面が表示されませんでしたか?

    Vite

    vite.png

    Kestrel

    Kestrel.png

    上の画像では、Vite のポートが 5173、Kestrel のポートが 7133 に設定されています。

    なので、App.jsx でやっているように React から ASP.NET Core の API に fetch で要求をかけるとクロスドメインの問題が出ます。

    その問題を解決するためにプロキシを使っていて、React からはプロキシ経由で ASP.NET Core の API に要求が出ます。(プロキシの設定は vite.config.js で行っているようです)

    一方「参考にしたMicrosoft情報サイト (上記) に示されてるサンプル」はプロキシなど使ってなくて、クライアントは直接 Kestrel とやり取りしていると思います。

    同じサンプルを、最新のテンプレートで作る React + ASP.NET Core アプリに移植して動かないということですと、プロキシの有無が影響しているのかもと思った次第です。

    ただ、ググって調べた限りですが、Vite のプロキシは WebSocket に対応しているという記事も目にしましたので、違うかもしれませんが。

  7. @ich0

    Questioner

    解説ありがとうございます。新しいテンプレートで便利な Vite 利用が加わったことは知っていましたが、それがどう絡むか理解していませんでした。ようやくそちらに目が向き、類似問題の解決事例を検索・調査することができました。結果、vite.config.js のサーバーオプションのプロキシに下記コードを追加することで当方問題も解決しました。

    ...
    
    // https://vitejs.dev/config/
    export default defineConfig({
        plugins: [plugin()],
        resolve: {
            alias: {
                '@': fileURLToPath(new URL('./src', import.meta.url))
            }
        },
        server: {
            proxy: {
                '^/weatherforecast': {
                    target: 'https://localhost:7086/',
                    secure: false
                },
    +            '^/ws': {
    +                target: 'wss://localhost:7086/',
    +                secure: false,
    +                ws: true
                }
            },
            port: 5173,
            https: {
                key: fs.readFileSync(keyFilePath),
                cert: fs.readFileSync(certFilePath),
            },
        }
    })
    
    

    上記を実施することで、WebSocket 通信が下記の通り正常に動作するようになりました。(質問の再現手順を更新しています。)
    image.png

    ありがとうございました。

クライアント側で WebSocket オブジェクトが生成され、WebSocket 接続の要求をしているが、このまま何の変化もなく接続が成立しないままとなる。

タイムアウト等のエラーにもならない? のが不思議です。。。

'ws://localhost:8080/'だと思うのですが、組み立てたurlは正しいでしょうか? 

0Like

Comments

  1. @ich0

    Questioner

    Q&Aにある再現手順を更新しました。
    4分間ほど待つと、Code 1006 で通信がクローズします。恐らくタイムアウトエラーの意味かと推測します。

    WebSocket 接続先の URL は "wss://localhost:5173/ws" です。この URL の組み立ては下記のサンプルコード の 65~68 行目を参考にしています。

    なお、このサンプルコードは正しく動作しました。サンプルは React による SPA (Single Page Application) ではなく単純な WebAPI アプリですので、そこが今回質問させて頂いたものとは状況が異なる部分になっています。

  2. @ich0

    Questioner

    ちなみにWebSocket 接続先の URL を "wss://localhost:8080/ws" にしてみたところ、4分間ではなく 3~5 秒程度で直ぐに異常クローズします。

    image.png

  3. WebSocket 接続先の URL は "wss://localhost:5173/ws" です

    ポート番号:5173が正しいのなら、urlにws://localhost:5173/wsを試してみませんか。
    (Reactなら3000という記事が多い)
    ポート番号はnetstatで確認できると思います。

  4. @ich0

    Questioner

    ポート番号:5173が正しいのなら、urlにws://localhost:5173/wsを試してみませんか。

    試してみましたが、WebSocket 接続要求は直ぐに (1~2秒ほどで) Code 1006 で異常クローズとなります。
    image.png

Your answer might help someone💌