LoginSignup
1
0

More than 5 years have passed since last update.

VaporでWebSocketチャットサーバを作る

Posted at

SwiftのWebフレームワークであるVaporを使って,WebSocketチャットサーバを作ってみました.

環境

  • Vapor 3.1
  • swift 4.2.1
  • Xcode 10.1
  • macOS 10.14 (Mojave)

WebSocketライブラリの追加

Vaporの新規プロジェクトを作成し,WebSocketライブラリをPackage.swiftに入れます.
(apiテンプレートを元にしていますが,テンプレートで提供しているAPIサーバ機能は今回使っていません).

$ vapor new --api vapor-websocket
Pakage.swift
// swift-tools-version:4.0
import PackageDescription

let package = Package(
    name: "vapor-websocket",
    dependencies: [
        // 💧 A server-side Swift web framework.
        .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"),

        // 🔵 Swift ORM (queries, models, relations, etc) built on SQLite 3.
        .package(url: "https://github.com/vapor/fluent-sqlite.git", from: "3.0.0"),
        .package(url: "https://github.com/vapor/websocket.git", from: "1.0.0"),
    ],
    targets: [
        .target(name: "App", dependencies: ["FluentSQLite", "Vapor", "WebSocket"]),
        .target(name: "Run", dependencies: ["App"]),
        .testTarget(name: "AppTests", dependencies: ["App"])
    ]
)

WebSocketチャットサーバ機能の追加

Sources/App/chat.swiftというファイルを新規作成し,以下のようなチャットルームクラスを作りました.

chat.swift
import Vapor

public class ChatRoom {
  // 接続中のWebSocketクライアント
  var clients = [WebSocket]()

  // WebSocketクライアントの接続ハンドラ
  func handler() -> ((WebSocket, Request) throws -> ()) {
    return { ws, req in
      // 接続中クライアントリストに追加
      self.clients.append(ws)
      // メッセージ受信時のハンドラを登録
      ws.onText(self.onText)
      // 切断時にクライアントリストから除去
      ws.onClose.always {
        self.clients = self.clients.filter { $0 === ws }
      }
    }
  }

  // WebSocketクライアントからのメッセージハンドラ
  private func onText(sender: WebSocket, text: String) -> () {
    // 送られたメッセージをそのまま全クライアントに送信する
    self.clients.forEach{ ws in
      if ws !== sender {
        ws.send("> \(text)")
      }
    }
  }
}
  • 接続中のWebSocketクライアントのリストをclientsで保持し,接続・切断で追加・除去しています
  • メッセージの受信時に,送信元以外のクライアントにメッセージを転送しています

これを,Sources/App/configure.swiftでサービスに追加します.

configure.swift
    // WebSocketサーバの作成
    let wss = NIOWebSocketServer.default()

    // WebSocketアップグレードサポートを/echoに追加
    let chatRoom = ChatRoom()
    wss.get("echo", use: chatRoom.handler())

    // WebSocketサーバの登録
    services.register(wss, as: WebSocketServer.self)

実行

サーバを起動し,wstaで動作確認します.

$ vapor build && vapor run
Running vapor-websocket ...
[ INFO ] Migrating 'sqlite' database (/Users/hoge/sandbox/vapor-websocket/.build/checkouts/fluent.git--929032494386059648/Sources/Fluent/Migration/MigrationConfig.swift:69)
[ INFO ] Preparing migration 'Todo' (/Users/hoge/sandbox/vapor-websocket/.build/checkouts/fluent.git--929032494386059648/Sources/Fluent/Migration/Migrations.swift:111)
[ INFO ] Migrations complete (/Users/hoge/sandbox/vapor-websocket/.build/checkouts/fluent.git--929032494386059648/Sources/Fluent/Migration/MigrationConfig.swift:73)
Running default command: .build/debug/Run serve
Server starting on http://localhost:8080
クライアント1
$ wsta ws://localhost:8080/echo
Connected to ws://localhost:8080/echo
Hi, my name is client1.
> Hi! I'm client2!
クライアント2
$ wsta ws://localhost:8080/echo
Connected to ws://localhost:8080/echo
> Hi, my name is client1.
Hi! I'm client2!

両方のクライアントで接続後,クライアント1からHi, my name is client1.と送信し,その後クライアント2からHi! I'm client2!と送信しました.

注意点

WebSocketクライアントからのメッセージ受信時はonTextでハンドラを登録しますが,切断時はonCloseFutureで非同期処理する必要があり,書き方が異なります.
エラー時はonTextと同様にonErrorでハンドラを登録します.

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0