Context
I'm developing a Mac app. In this app, I want to run a websocket server. To do this, I'm using Swift NIO and Websocket-Kit. My full setup is below.
Question
All of the documentation for Websocket-Kit and SwiftNIO is geared towards a creating a single server-side process that starts up when you launch it from the command line and then runs infinitely.
In my app, I must be able to start the websocket server and then shut it down and restart it on demand, without re-launching my application. The code below does that, but I would like confirmation of two things:
In the test() function, I send some text to all connected clients. I am unsure if this is thread-safe and correct. Can I store the WebSocket instances as I'm doing here and message them from the main thread of my application?
Am I shutting down the websocket server correctly? The result of the call to serverBootstrap(group:)[...].bind(host:port:).wait() creates a Channel and then waits infinitely. When I call shutdownGracefully() on the associated EventLoopGroup, is that server cleaned up correctly? (I can confirm that port 5759 is free again after this shutdown, so I'm guessing everything is cleaned up?)
Thanks for the input; it's tough to find examples of using SwiftNIO and Websocket-Kit inside an application.
Code
import Foundation
import NIO
import NIOHTTP1
import NIOWebSocket
import WebSocketKit
#objc class WebsocketServer: NSObject
{
private var queue: DispatchQueue?
private var eventLoopGroup: MultiThreadedEventLoopGroup?
private var websocketClients: [WebSocket] = []
#objc func startServer()
{
queue = DispatchQueue.init(label: "socketServer")
queue?.async
{
let upgradePipelineHandler: (Channel, HTTPRequestHead) -> EventLoopFuture<Void> = { channel, req in
WebSocket.server(on: channel) { ws in
ws.send("You have connected to WebSocket")
DispatchQueue.main.async {
self.websocketClients.append(ws)
print("websocketClients after connection: \(self.websocketClients)")
}
ws.onText { ws, string in
print("received")
ws.send(string.trimmingCharacters(in: .whitespacesAndNewlines).reversed())
}
ws.onBinary { ws, buffer in
print(buffer)
}
ws.onClose.whenSuccess { value in
print("onClose")
DispatchQueue.main.async
{
self.websocketClients.removeAll { (socketToTest) -> Bool in
return socketToTest === ws
}
print("websocketClients after close: \(self.websocketClients)")
}
}
}
}
self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2)
let port: Int = 5759
let promise = self.eventLoopGroup!.next().makePromise(of: String.self)
let server = try? ServerBootstrap(group: self.eventLoopGroup!)
// Specify backlog and enable SO_REUSEADDR for the server itself
.serverChannelOption(ChannelOptions.backlog, value: 256)
.serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.childChannelInitializer { channel in
let webSocket = NIOWebSocketServerUpgrader(
shouldUpgrade: { channel, req in
return channel.eventLoop.makeSucceededFuture([:])
},
upgradePipelineHandler: upgradePipelineHandler
)
return channel.pipeline.configureHTTPServerPipeline(
withServerUpgrade: (
upgraders: [webSocket],
completionHandler: { ctx in
// complete
})
)
}.bind(host: "0.0.0.0", port: port).wait()
_ = try! promise.futureResult.wait()
}
}
///
/// Send a message to connected clients, then shut down the server.
///
#objc func test()
{
self.websocketClients.forEach { (ws) in
ws.eventLoop.execute {
ws.send("This is a message being sent to all websockets.")
}
}
stopServer()
}
#objc func stopServer()
{
self.websocketClients.forEach { (ws) in
try? ws.eventLoop.submit { () -> Void in
print("closing websocket: \(ws)")
_ = ws.close()
}.wait() // Block until complete so we don't shut down the eventLoop before all clients get closed.
}
eventLoopGroup?.shutdownGracefully(queue: .main, { (error: Error?) in
print("Eventloop shutdown now complete.")
self.eventLoopGroup = nil
self.queue = nil
})
}
}
In the test() function, I send some text to all connected clients. I am unsure if this is thread-safe and correct. Can I store the WebSocket instances as I'm doing here and message them from the main thread of my application?
Exactly as you're doing here, yes, that should be safe. ws.eventLoop.execute will execute that block on the event loop thread belonging to that WebSocket connection. This will be safe.
When I call shutdownGracefully() on the associated EventLoopGroup, is that server cleaned up correctly? (I can confirm that port 5759 is free again after this shutdown, so I'm guessing everything is cleaned up?)
Yes. shutdownGracefully forces all connections and listening sockets closed.
Related
I am trying to work with a gRPC api and I have to send credentials securely. I am having issues to figure this out. I am using the swift-grpc library. I will link the docs and maybe someone can explain what I am supposed to do.
I am still unsure of what makes this secure through ssl(are we sending certificates).
docs from the swift-grpc using tlc library here
If anyone can give an explenation of how the ssl works and what to do that would be great
// code
import Foundation
import GRPC
import NIO
class Networking {
var authServiceClient: PartnerApi2_PartnerApiClient?
let port: Int = 50052
init() {
// build a fountain of EventLoops
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
do {
// open a channel to the gPRC server
let channel = try GRPCChannelPool.with(
target: .host("partner-api.com", port: self.port),
transportSecurity: .plaintext,
eventLoopGroup: eventLoopGroup
)
// create a Client
self.authServiceClient = PartnerApi2_PartnerApiClient.init(channel: channel) //AuthService_AuthServiceRoutesClient(channel: channel)
print("grpc connection initialized")
login(username: "email", password: "password")
} catch {
print("Couldn’t connect to gRPC server")
}
}
func login(username: String, password: String) -> String {
print("Login: username=\(username)")
// build the AccountCredentials object
let accountCredentials: PartnerApi2_AuthenticationRequest = .with {
$0.partnerEmail = username
$0.password = password
}
// grab the login() method from the gRPC client
let call = self.authServiceClient!.authenticate(accountCredentials)
// prepare an empty response object
let oauthCredentials: PartnerApi2_AuthenticationResponse
// execute the gRPC call and grab the result
do {
oauthCredentials = try call.response.wait()
} catch {
print("RPC method ‘login’ failed: \(error)")
// it would be better to throw an error here, but
// let’s keep this simple for demo purposes
return ""
}
// Do something interesting with the result
let oauthToken = oauthCredentials.authToken
print("Logged in with oauth token \(oauthToken)")
// return a value so we can use it in the app
return oauthToken
}
}
As I port some Objective-C code to Swift, I'm trying to better understand the new Combine framework and how I can use it to re-create a common design pattern.
In this case, the design pattern is a single object (Manager, Service, etc) that any number of "clients" can register with as a delegate to receive callbacks. It's a basic 1:Many pattern using delegates.
Combine looks ideal for this, but the sample code is a bit thin. Below is a working example but I'm not sure if it's correct or being used as intended. In particular, I'm curious about reference cycles between the objects.
class Service {
let tweets = PassthroughSubject<String, Never>()
func start() {
// Simulate the need send to send updates.
DispatchQueue.global(qos: .utility).async {
while true {
self.sendTweet()
usleep(100000)
}
}
}
func sendTweet() {
tweets.send("Message \(Date().timeIntervalSince1970)")
}
}
class Client : Subscriber {
typealias Input = String
typealias Failure = Never
let service:Service
var subscription:Subscription?
init(service:Service) {
self.service = service
// Is this a retain cycle?
// Is this thread-safe?
self.service.tweets.subscribe(self)
}
func receive(subscription: Subscription) {
print("Received subscription: \(subscription)")
self.subscription = subscription
self.subscription?.request(.unlimited)
}
func receive(_ input: String) -> Subscribers.Demand {
print("Received tweet: \(input)")
return .unlimited
}
func receive(completion: Subscribers.Completion<Never>) {
print("Received completion")
}
}
// Dependency injection is used a lot throughout the
// application in a similar fashion to this:
let service = Service()
let client = Client(service:service)
// In the real world, the service is started when
// the application is launched and clients come-and-go.
service.start()
Output is:
Received subscription: PassthroughSubject
Received tweet: Message 1560371698.300811
Received tweet: Message 1560371698.4087949
Received tweet: Message 1560371698.578027
...
Is this even remotely close to how Combine was intended to be used?
lets check it! the simplest way is add deinit to both classes and limit the live of service
class Service {
let tweets = PassthroughSubject<String, Never>()
func start() {
// Simulate the need send to send updates.
DispatchQueue.global(qos: .utility).async {
(0 ... 3).forEach { _ in
self.sendTweet()
usleep(100000)
}
}
}
func sendTweet() {
tweets.send("Message \(Date().timeIntervalSince1970)")
}
deinit {
print("server deinit")
}
}
now it is easy to check that
do {
let service = Service()
//_ = Client(service:service)
// In the real world, the service is started when
// the application is launched and clients come-and-go.
service.start()
}
will finished as expected
server deinit
modify it with subscribing client
do {
let service = Service()
_ = Client(service:service)
service.start()
}
and you immediately know the result
Received subscription: PassthroughSubject
Received tweet: Message 1580816649.7355099
Received tweet: Message 1580816649.8548698
Received tweet: Message 1580816650.001649
Received tweet: Message 1580816650.102639
there is a memory cycle, as you expected :-)
Generally, there is very low probability, that you need your own subscriber implementation.
First modify the service, so the client will know when no more messages will arrive
func start() {
// Simulate the need send to send updates.
DispatchQueue.global(qos: .utility).async {
// send some tweets
(0 ... 3).forEach { _ in
self.sendTweet()
usleep(100000)
}
// and send "finished"
self.tweets.send(completion: .finished)
}
}
and next use "build-in" subcriber in your publisher by invoking his .sink method. .sink return AnyCancelable (it is a reference type) which you have to store somewhere.
var cancelable: AnyCancellable?
do {
let service = Service()
service.start()
// client
cancelable = service.tweets.sink { (s) in
print(s)
}
}
now, everythig works, es expected ...
Message 1580818277.2908669
Message 1580818277.4674711
Message 1580818277.641886
server deinit
But what about cancelable? Let check it!
var cancelable: AnyCancellable?
do {
let service = Service()
service.start()
// client
cancelable = service.tweets.sink { (s) in
print(s)
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
print(cancelable)
}
it prints
Message 1580819227.5750608
Message 1580819227.763901
Message 1580819227.9366078
Message 1580819228.072041
server deinit
Optional(Combine.AnyCancellable)
so you have to release it "manualy", if you don't need it anymore. .sink is there again!
var cancelable: AnyCancellable?
do {
let service = Service()
service.start()
// client
cancelable = service.tweets.sink(receiveCompletion: { (completion) in
print(completion)
// this inform publisher to "unsubscribe" (not necessery in this scenario)
cancelable?.cancel()
// and we can release our canceleble
cancelable = nil
}, receiveValue: { (message) in
print(message)
})
}
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
print(cancelable)
}
and result
Message 1580819683.462331
Message 1580819683.638145
Message 1580819683.74383
finished
server deinit
nil
Combine has almost everything you need in real word application, the trouble is a lack of documentation, but a lot of sources are available on the internet.
Custom Combine Subscriber should also conform to Cancellable protocol that provides a method to forward cancellation to the received subscription object from Publisher. That way you do not have to expose Subscription property. According to doc:
If you create a custom Subscriber, the publisher sends a Subscription object when you first subscribe to it. Store this subscription, and then call its cancel() method when you want to cancel publishing. When you create a custom subscriber, you should implement the Cancellable protocol, and have your cancel() implementation forward the call to the stored subscription.
https://developer.apple.com/documentation/combine/receiving_and_handling_events_with_combine
I am having an issue where, when sending a message through WCConnection, the session.sendMessage fails sometimes if called in the delegate method activationDidCompleteWith. The issue is not repeatable every time (in fact, it works most of the time).
But forcing a session.sendMessage by using a button in my UI (calling the identical loading code) has a successful session communication immediately, so I know the issue is not in the session itself or the master app.
Is it unsafe to assume the session is ready to accept communication in activationDidCompleteWith? Is there a better place to be calling my initial communication?
In my experience watch OS is pretty finicky, especially when using older model watches. That being said I think the answer to the question: "Is it unsafe to assume the session is ready to accept communication in activationDidCompleteWith?" is yes, it is unsafe to assume that.
In my own app I have a very similar case to yours and I solved it by sending a message until a response is received.
// false until a response is received from the phone
let receivedResponse: Bool = false
// function that sends the message
func requestResponse() {
guard WCSession.default.isReachable else {
print("Phone not reachable")
return
}
// callback that handles response
let responseHandler: ([String: Any]) -> () = { response in
receivedResponse = true
callback(response)
}
WCSession.default.sendMessage(["Request": "Response"],
replyHandler: responseHandler) { error in
print(error.localizedDescription)
}
}
// timer that calls the request function repeatedly
let retryTimer = Timer.scheduledTimer(withTimeInterval: 1,
repeats: true) { timer in
if receivedResponse {
// we know we got a response so clean up timer
timer.invalidate()
}
requestResponse()
}
I have a test that opens and listens to a Unix Domain Socket. The socket is opened and reads data without issues, but it doesn't shutdown gracefully.
This is the error I get when I try to run the test a second time:
thread 'test_1' panicked at 'called Result::unwrap() on an Err
value: Error { repr: Os { code: 48, message: "Address already in use"
} }', ../src/libcore/result.rs:799 note: Run with RUST_BACKTRACE=1
for a backtrace.
The code is available at the Rust playground and there's a Github Gist for it.
use std::io::prelude::*;
use std::thread;
use std::net::Shutdown;
use std::os::unix::net::{UnixStream, UnixListener};
Test Case:
#[test]
fn test_1() {
driver();
assert_eq!("1", "2");
}
Main entry point function
fn driver() {
let listener = UnixListener::bind("/tmp/my_socket.sock").unwrap();
thread::spawn(|| socket_server(listener));
// send a message
busy_work(3);
// try to disconnect the socket
let drop_stream = UnixStream::connect("/tmp/my_socket.sock").unwrap();
let _ = drop_stream.shutdown(Shutdown::Both);
}
Function to send data in intervals
#[allow(unused_variables)]
fn busy_work(threads: i32) {
// Make a vector to hold the children which are spawned.
let mut children = vec![];
for i in 0..threads {
// Spin up another thread
children.push(thread::spawn(|| socket_client()));
}
for child in children {
// Wait for the thread to finish. Returns a result.
let _ = child.join();
}
}
fn socket_client() {
let mut stream = UnixStream::connect("/tmp/my_socket.sock").unwrap();
stream.write_all(b"hello world").unwrap();
}
Function to handle data
fn handle_client(mut stream: UnixStream) {
let mut response = String::new();
stream.read_to_string(&mut response).unwrap();
println!("got response: {:?}", response);
}
Server socket that listens to incoming messages
#[allow(unused_variables)]
fn socket_server(listener: UnixListener) {
// accept connections and process them, spawning a new thread for each one
for stream in listener.incoming() {
match stream {
Ok(mut stream) => {
/* connection succeeded */
let mut response = String::new();
stream.read_to_string(&mut response).unwrap();
if response.is_empty() {
break;
} else {
thread::spawn(|| handle_client(stream));
}
}
Err(err) => {
/* connection failed */
break;
}
}
}
println!("Breaking out of socket_server()");
drop(listener);
}
Please learn to create a minimal reproducible example and then take the time to do so. In this case, there's no need for threads or functions or testing frameworks; running this entire program twice reproduces the error:
use std::os::unix::net::UnixListener;
fn main() {
UnixListener::bind("/tmp/my_socket.sock").unwrap();
}
If you look at the filesystem before and after the test, you will see that the file /tmp/my_socket.sock is not present before the first run and it is present before the second run. Deleting the file allows the program to run to completion again (at which point it recreates the file).
This issue is not unique to Rust:
Note that, once created, this socket file will continue to exist, even after the server exits. If the server subsequently restarts, the file prevents re-binding:
[...]
So, servers should unlink the socket pathname prior to binding it.
You could choose to add some wrapper around the socket that would automatically delete it when it is dropped or create a temporary directory that is cleaned when it is dropped, but I'm not sure how well that would work. You could also create a wrapper function that deletes the file before it opens the socket.
Unlinking the socket when it's dropped
use std::path::{Path, PathBuf};
struct DeleteOnDrop {
path: PathBuf,
listener: UnixListener,
}
impl DeleteOnDrop {
fn bind(path: impl AsRef<Path>) -> std::io::Result<Self> {
let path = path.as_ref().to_owned();
UnixListener::bind(&path).map(|listener| DeleteOnDrop { path, listener })
}
}
impl Drop for DeleteOnDrop {
fn drop(&mut self) {
// There's no way to return a useful error here
let _ = std::fs::remove_file(&self.path).unwrap();
}
}
You may also want to consider implementing Deref / DerefMut to make this into a smart pointer for sockets:
impl std::ops::Deref for DeleteOnDrop {
type Target = UnixListener;
fn deref(&self) -> &Self::Target {
&self.listener
}
}
impl std::ops::DerefMut for DeleteOnDrop {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.listener
}
}
Unlinking the socket before it's opened
This is much simpler:
use std::path::Path;
fn bind(path: impl AsRef<Path>) -> std::io::Result<UnixListener> {
let path = path.as_ref();
std::fs::remove_file(path)?;
UnixListener::bind(path)
}
Note that you can combine the two solutions, such that the socket is deleted before creation and when it's dropped.
I think that deleting during creation is a less-optimal solution: if you ever start a second server, you'll prevent the first server from receiving any more connections. It's probably better to error and tell the user instead.
Recently, I found a pure swift socket server and client called IBM BlueSocket.
It is suitable for me that it does server-cleint communication.
It has a pretty simple sample. but I encountered some problems.
1. How to run it on a GUI application's run loop?
2. How to run it and support multi connections?
For what it's worth, I present the world's simplest chat client.
import Foundation
import Socket
// Very simplified chat client for BlueSocket - no UI, it just connects to the echo server,
// exchanges a couple of messages and then disconnects.
// You can run two instances of Xcode on your Mac, with the BlueSocketEchoServer running in one and
// this program running in the other. It has been tested running in the iPhone simulator, i.e.,
// under iOS, without problems.
// License: Public domain.
public class BlueSocketChatClient {
public func runClient() {
do {
let chatSocket = try Socket.create(family: .inet6)
try chatSocket.connect(to: "127.0.0.1", port: 1337)
print("Connected to: \(chatSocket.remoteHostname) on port \(chatSocket.remotePort)")
try readFromServer(chatSocket)
try chatSocket.write(from: "Hello to you too!")
try readFromServer(chatSocket)
try chatSocket.write(from: "Bye now!\n")
try chatSocket.write(from: "QUIT")
sleep(1) // Be nice to the server
chatSocket.close()
}
catch {
guard let socketError = error as? Socket.Error else {
print("Unexpected error ...")
return
}
print("Error reported:\n \(socketError.description)")
}
}
// This is very simple-minded. It blocks until there is input, and it then assumes that all the
// relevant input has been read in one go.
func readFromServer(_ chatSocket : Socket) throws {
var readData = Data(capacity: chatSocket.readBufferSize)
let bytesRead = try chatSocket.read(into: &readData)
guard bytesRead > 0 else {
print("Zero bytes read.")
return
}
guard let response = String(data: readData, encoding: .utf8) else {
print("Error decoding response ...")
return
}
print(response)
}
}
Bill Abt: If you can use this in any way you're welcome to it.
The sample has been recently updated and illustrates use of the GCD based Dispatch API to do multi-threading and supporting multiple connections. It also should give you a idea on how to run it on the main queue (which'll work for either a GUI or server application).