💎

それ、F#でやってみました - WebSocket (サーバ:ASP.NET Core + クライアント:Bun 1.1)

2024/05/08に公開

ASP.NET Core Minimal APIを使ってF#で受信した内容をそのまま送り返すWebSocketサーバを作成してみました。WebSocketのクライアントはBun1.1で作成しました。

サーバ:Bun 1.1 + クライアント:.NET ClientWebSocketはこちら

WebSocketサーバ(Program.fs)
// ASP.NET Core での Websocket のサポート
// https://learn.microsoft.com/ja-jp/aspnet/core/fundamentals/websockets?view=aspnetcore-9.0

// WebSocketサーバ 受信したものをそのまま返す(エコー)
// クライアントから先に切断すること
// .NET 9 Preview 3 対応

// dotnet new web -lang f# -o ディレクトリ名
// cd ディレクトリ名
// (Program.fsを編集)
// dotnet run

open System
open System.Net.WebSockets
open System.Text
open System.Threading
open System.Threading.Tasks
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Http

// WebSocketサーバ(エコー)
let echo (websocket: WebSocket) = task {
    // 受信バッファ
    let buffer = Array.zeroCreate<byte> (1024 * 4)
    // WebSocket受信
    let mutable receiveSocket = websocket.ReceiveAsync(
        new ArraySegment<byte>(buffer), CancellationToken.None).Result
    while (not receiveSocket.CloseStatus.HasValue) do
        // 受信した内容を表示
        printfn "Received: %s" <|
            Encoding.UTF8.GetString(buffer[0..receiveSocket.Count])
        // 受信した内容を送り返す(エコー)
        websocket.SendAsync(
            new ArraySegment<byte>(buffer, 0, receiveSocket.Count),
            receiveSocket.MessageType,
            receiveSocket.EndOfMessage,
            CancellationToken.None) |> ignore
        // WebSocket受信
        receiveSocket <- websocket.ReceiveAsync(
            new ArraySegment<byte>(buffer), CancellationToken.None).Result
    // クローズ
    websocket.CloseAsync(
        receiveSocket.CloseStatus.Value,
        receiveSocket.CloseStatusDescription,
        CancellationToken.None) |> ignore
}

// メイン(エントリーポイント)
[<EntryPoint>]
let main args =
    let url = "http://127.0.0.1:5080"

    let builder = WebApplication.CreateBuilder(args)
    let app = builder.Build()

    // WebSocket設定
    app.UseWebSockets() |> ignore
    // ミドルウェア(middleware)
    app.Use(Func<HttpContext, RequestDelegate, Task>(fun ctx next ->
        // URLの末尾が/wsのとき
        if (ctx.Request.Path.Value = "/ws") then
            // WebSocketを受信したら
            if (ctx.WebSockets.IsWebSocketRequest) then
                // 受信した内容を取得
                let websocket = ctx.WebSockets.AcceptWebSocketAsync().Result
                // エコー処理
                echo websocket
            else
                // WebSocketでなければステータスコード400
                task {
                    ctx.Response.StatusCode <- StatusCodes.Status400BadRequest
                }
        else
            // 次のミドルウェアへ
            next.Invoke(ctx)
    )) |> ignore

    // 実行
    app.Run url

    0 // Exit code
WebSocketクライアント(Bun1.1)
// Read from stdin with Bun
// https://bun.sh/guides/process/stdin
// Bun 1.1 WebSocket is stable
// https://bun.sh/blog/bun-v1.1#websocket-is-stable

// bun run websocket_example.ts

// WebSocketクライアント サーバに文字列を送信、受信した文字列を表示
// クライアントから先に切断すること
// Bun 1.1対応

const url = "ws://127.0.0.1:5080/ws";   // アクセスするサーバ
const prompt = "ws> ";                  // メッセージ入力プロンプト

// WebSocketサーバにアクセス
const ws = new WebSocket(url);

// サーバから受信したとき
ws.addEventListener("message", ({data}) => {
    console.log("Received:", data);     // 受信した内容を表示
    process.stdout.write(prompt);       // プロンプトを表示
});

// サーバの接続したとき
ws.addEventListener("open", async () => {
    process.stdout.write(prompt);       // プロンプトを表示
    for await (const line of console) { // ターミナル画面から1行ずつ入力
        if (line == "") break;  // 何も入力されなければ終了
        ws.send(line);          // 入力された文字列をサーバに文字列を送信
    }
    ws.close(); // クローズ
});
結果
// クライアント
ws> こんにちはWebSocket
Received: こんにちはWebSocket
ws> WebSocketサーバに送信
Received: WebSocketサーバに送信
ws> サーバに送信した内容がそのまま送り返される
Received: サーバに送信した内容がそのまま送り返される
ws> (入力せずEnterで終了)

// サーバ
Received: こんにちはWebSocket
Received: WebSocketサーバに送信
Received: サーバに送信した内容がそのまま送り返される

Discussion