slidenumber: true autoscale: true
-
ウェブブラウザのJavaScriptから使える汎用トランスポートプロトコル
-
リアルタイム双方向通信
- 利用例: Slackとか
iOS13とmacOS CatalinaでWebSocketサポートが追加された
-
Foundation / URLSession
- Clientのみ
-
Network.framework
- Server, Client両対応
- TCP / TLS 上で動く
- HTTP / HTTPS と共存できる
- メッセージベース
- 信頼性
- 順序保証
- プロキシサポート
- Text / Binary
- Ping / Pong
- フレーミング
- サブプロトコル
open class URLSession {
open func webSocketTask(with url: URL)
-> URLSessionWebSocketTask
open func webSocketTask(with url: URL, protocols: [String])
-> URLSessionWebSocketTask
open func webSocketTask(with request: URLRequest)
-> URLSessionWebSocketTask
}
open class URLSessionTask {
open func resume()
}
public protocol URLSessionWebSocketDelegate :
URLSessionTaskDelegate
{
optional func urlSession(_ session: URLSession,
webSocketTask: URLSessionWebSocketTask,
didOpenWithProtocol protocol: String?)
}
public protocol URLSessionTaskDelegate : URLSessionDelegate {
optional func urlSession(_ session: URLSession,
task: URLSessionTask,
didCompleteWithError error: Error?)
}
extension URLSessionWebSocketTask {
public enum Message {
case data(Data)
case string(String)
}
public func send(_ message: URLSessionWebSocketTask.Message,
completionHandler: @escaping (Error?) -> Void)
}
extension URLSessionWebSocketTask {
public func receive(
completionHandler:
@escaping (Result<URLSessionWebSocketTask.Message,
Error>) -> Void)
}
open class URLSessionWebSocketTask {
open func cancel(with
closeCode: URLSessionWebSocketTask.CloseCode,
reason: Data?)
}
public protocol URLSessionWebSocketDelegate {
optional func urlSession(_ session: URLSession,
webSocketTask: URLSessionWebSocketTask,
didCloseWith closeCode: URLSessionWebSocketTask.CloseCode,
reason: Data?)
}
open class URLSessionTask {
open func cancel()
}
public protocol URLSessionTaskDelegate {
optional func urlSession(_ session: URLSession,
task: URLSessionTask,
didCompleteWithError error: Error?)
}
open class URLSessionWebSocketTask {
open func sendPing(
pongReceiveHandler: @escaping (Error?) -> Void)
}
var request: URLRequest = ...
request.addValue("chat",
forHTTPHeaderField: "Sec-WebSocket-Protocol")
final public class NWConnection {
public init(to: NWEndpoint, using: NWParameters)
public func start(queue: DispatchQueue)
}
let options = NWProtocolWebSocket.Options()
let parameters = NWParameters.tcp
parameters.defaultProtocolStack.applicationProtocols = [
options
]
let connection = NWConnection(to: endpoint, using: parameters)
final public class NWConnection {
public enum State {
case setup
case waiting(NWError)
case preparing
case ready
case failed(NWError)
case cancelled
}
public var stateUpdateHandler: ((NWConnection.State) -> Void)?
}
final public class NWConnection {
public enum SendCompletion {
case contentProcessed((NWError?) -> Void)
case idempotent
}
public func send(content: Data?,
contentContext: NWConnection.ContentContext,
isComplete: Bool,
completion: NWConnection.SendCompletion)
}
let metadata = NWProtocolWebSocket.Metadata(
opcode: NWProtocolWebSocket.Opcode.binary)
let context = NWConnection.ContentContext(identifier: "context",
metadata: [metadata])
public class NWProtocolWebSocket {
public enum Opcode : UInt8 {
case cont
case text
case binary
case close
case ping
case pong
}
}
Opcodeを使っていろいろと手動実装できるようになっている。
final public class NWConnection {
public func receiveMessage(
completion: @escaping (
Data?,
/* context: */ NWConnection.ContentContext?,
/* isCompleted: */ Bool,
NWError?) -> Void)
}
if let metadata = (context?.protocolMetadata
.compactMap { $0 as? NWProtocolWebSocket.Metadata }.first)
{
switch metadata.opcode {
case .text: ...
case .binary: ...
...
}
}
public class NWProtocolWebSocket {
public class Metadata {
public let opcode: NWProtocolWebSocket.Opcode
public var closeCode: NWProtocolWebSocket.CloseCode
}
}
Metadata, Opcode, send, receiveで実装する
final public class NWConnection {
public func cancel()
}
TCPとしては丁寧な切断
final public class NWConnection {
public func forceCancel()
}
public class NWProtocolWebSocket {
public class Metadata : NWProtocolMetadata {
public let opcode: NWProtocolWebSocket.Opcode
public func setPongHandler(
_ queue: DispatchQueue,
handler: @escaping ((NWError?) -> Void))
}
}
Metadata, Opcode, send, receiveで実装する pongハンドラはある
自動実装がある
public class NWProtocolWebSocket {
public class Options {
public var autoReplyPing: Bool
}
}
public class NWProtocolWebSocket {
public class Options {
// クライアントリクエスト
public func setSubprotocols(_ subprotocols: [String])
}
public class Metadata {
// サーバ採用
public var selectedSubprotocol: String? { get }
}
}
final public class NWConnection {
// 多分これ
public func metadata(definition: NWProtocolDefinition) -> NWProtocolMetadata?
}
final public class NWListener {
public init(using: NWParameters, on: NWEndpoint.Port) throws
public func start(queue: DispatchQueue)
}
let options = NWProtocolWebSocket.Options()
let parameters = NWParameters.tcp
parameters.defaultProtocolStack.applicationProtocols = [
options
]
let listener = try NWListener(using: parameters, on: port)
final public class NWListener {
public enum State {
case setup
case waiting(NWError)
case ready
case failed(NWError)
case cancelled
}
public var stateUpdateHandler: ((NWListener.State) -> Void)?
}
final public class NWListener {
public var newConnectionHandler: ((NWConnection) -> Void)?
}
NWConnectionが受け取れるので後はClientと同じ。 startの呼び出しが必要なので注意。
public class NWProtocolWebSocket {
public class Options {
public func setClientRequestHandler(
_ queue: DispatchQueue,
handler: @escaping (
(
/* subprotocols: */ [String],
/* headers: */ [(name: String, value: String)]
) -> NWProtocolWebSocket.Response
)
)
}
}
ネゴシエーションを自作できる
public class NWProtocolWebSocket {
public struct Response {
public enum Status {
case accept
case reject
}
public init(status: NWProtocolWebSocket.Response.Status,
subprotocol: String?,
additionalHeaders: [(name: String, value: String)]?)
}
}
https://github.com/omochi/WebSocketMacIOSDemo
-
Foundation / URL Loading System
https://developer.apple.com/documentation/foundation/url_loading_system
-
Network
https://developer.apple.com/documentation/network
-
Advances in Networking, Part 1
https://developer.apple.com/videos/play/wwdc2019/712/