How to connect Starscream iOS client to Java webserver - ios

I'm running Spring Boot v2.2 with a correct WebSocketHandler(). I'm confident the server is correct because when I go to http://websocket.org/echo.html and attempt to connect to our server, we can verify connection on both the server and the browser client.
However, in iOS (I'm testing on 2 simulators - iOS 12 and iOS 13.3), I'm not able to connect. I'm now attempting to utilize Starscream. (attempting this with Socket.io led to unsolvable issues and attempting this with SocketRocket led to issues simply getting it to build on iOS 13.3.)
The issue I'm facing now is that Starscream just fails silently when attempting to connect to the address of our java server (ws://127.0.0.1:8080/socket). When I say fail silently, I mean that nothing happens on the client or server indicating that there was an error but also by debugging I can see that isConnected = false on our iOS socket.
To attempt to fix this issue I've tried:
adding App Transport Security Settings -> Allow Arbitrary Loads = YES in Info.plist.
adding NSExceptionDomains -> NSExceptionAllowsInsecureHTTPLoads = YES in Info.plist.
utilizing both localhost and 127.0.0.1, both with /socket or / and HTTP instead of ws/wss.
I was even able to effectively query google.com with a GET request using native Swift.
import Foundation
import Starscream
class WinkNetworkClient : WebSocketDelegate {
private var isConnected : Bool = false
init() {
let socket: WebSocket =
WebSocket(request:
URLRequest(url: URL(string: "ws://127.0.0.1:8080/socket")!), certPinner: FoundationSecurity(allowSelfSigned: true))
socket.delegate = self
socket.connect()
// socket.write(string: "Hi Server!")
print("Client done")
}
func didReceive(event: WebSocketEvent, client: WebSocket) {
switch event {
case .connected(let headers):
isConnected = true
print("websocket is connected: \(headers)")
case .disconnected(let reason, let code):
isConnected = false
print("websocket is disconnected: \(reason) with code: \(code)")
case .text(let string):
print("Received text: \(string)")
case .binary(let data):
print("Received data: \(data.count)")
case .ping(_):
break
case .pong(_):
break
case .viabilityChanged(_):
break
case .reconnectSuggested(_):
break
case .cancelled:
isConnected = false
case .error(let error):
isConnected = false
print("error connecting to websocket: \(String(describing: error))")
}
}
}
I'm very lost as to what the issue might be. What am I doing wrong?

Related

How to create NWConnection

Premise
I'm using Apple's Network framework to create a peer-to-peer app. What I'm currently trying to do is:
Device A establishes a connection with Device B and sends data.
Device B saves the NWConnection information of Device A.
Device B establishes a connection with Device A again using the saved MWConnection information and sends data.
I modelled off Apple's demo, which is essentially this source code here. Unlike Apple's demo, which establishes a single two-way connection and maintains that connection at all times, my app connects to multiple devices that can drop in and out of the connections at any time. This is why I want to be able to a) distinguish a specific device and b) initiate a new connection.
Problem
The problem I'm having is being able to reconstruct the NWConnection object using the information provided when a connection has been established. There are two ways of instantiating this object.
init(host: NWEndpoint.Host, port: NWEndpoint.Port, using: NWParameters)
init(to: NWEndpoint, using: NWParameters)
My attempts have been trying to gather the endpoint information like the host and the port while the connection with the desired device has been established and instantiate NWEndpoint to be used in NWConnection. But, I haven't been able to re-establish the connection thus far.
Following is the portion of the object that is used to initiate a connection. My full code is modified, but Apple's counterpart can be seen here.
class PeerConnection {
weak var delegate: PeerConnectionDelegate?
weak var statusDelegate: StatusDelegate?
var connection: NWConnection?
let initiatedConnection: Bool
init(endpoint: NWEndpoint, interface: NWInterface?, passcode: String, delegate: PeerConnectionDelegate) {
self.delegate = delegate
self.initiatedConnection = true
let connection = NWConnection(to: endpoint, using: NWParameters(passcode: passcode))
self.connection = connection
startConnection()
}
init(connection: NWConnection, delegate: PeerConnectionDelegate) {
self.delegate = delegate
self.connection = connection
self.initiatedConnection = false
startConnection()
}
// Handle starting the peer-to-peer connection for both inbound and outbound connections.
func startConnection() {
guard let connection = connection else {
return
}
connection.stateUpdateHandler = { newState in
switch newState {
case .ready:
print("\(connection) established")
// When the connection is ready, start receiving messages.
self.receiveNextMessage()
// Notify your delegate that the connection is ready.
if let delegate = self.statusDelegate {
delegate.showConnectionStatus(.connectionSuccess("Connection Success"))
}
case .failed(let error):
print("\(connection) failed with \(error)")
// Cancel the connection upon a failure.
connection.cancel()
// Notify your delegate that the connection failed.
if let delegate = self.statusDelegate {
delegate.showConnectionStatus(.connectionFail("Connection Fail"))
}
default:
break
}
}
// Start the connection establishment.
connection.start(queue: .main)
}
}
This is how I receive data when another device sends it.
func receiveNextMessage() {
guard let connection = connection else {
return
}
/// Has to call itself again within the closure because the maximum message is once.
connection.receiveMessage { (content, context, isComplete, error) in
// Extract your message type from the received context.
if let message = context?.protocolMetadata(definition: GameProtocol.definition) as? NWProtocolFramer.Message {
self.delegate?.receivedMessage(content: content, message: message, connection: connection)
}
if error == nil {
// Continue to receive more messages until you receive and error.
self.receiveNextMessage()
}
}
}
Finally, following is how I attempt to reconstruct MWConnection:
func receivedMessage(content: Data?, message: NWProtocolFramer.Message, connection: NWConnection) {
switch(connection.endpoint) {
case .hostPort(let host, let port):
/// first attempt
let endpoint = NWEndpoint.hostPort(host: host, port: port)
let newConnection = PeerConnection(endpoint: endpoint, interface: nil, passcode: passcode, delegate: self)
/// second attempt
let connection = NWConnection(host: host, port: port, using: NWParameters(passcode: passcode))
let newConnection = PeerConnection(connection: connection, delegate: self)
default:
break
}
}
NWConnection is instantiated inside initializer of PeerConnection.

NWConnection timeout

this is my first post to the community, so please be patient ;-)
I am using Apples Network framework to build a TCP connection to a server. It locks like this:
let testConnection = NWConnection(host: NWEndpoint.Host(server!.serverAddress), port: NWEndpoint.Port(server!.serverPort)!, using: .tcp)
testConnection.stateUpdateHandler = ({ state in
print("TCP state change to: \(state)")
switch state {
case .setup:
break
case .waiting(let error):
print("Waiting Error \(error)")
testConnection.cancel()
break
case .preparing:
break
case .ready:
beginCommunication()
break
case .failed(let error):
print("\(error)")
break
case .cancelled:
break
default:
break
}
})
The serverAddress and serverPort is entered by the user. I want to test the connection. Now my problem is, that if the user is entering an invalid address / port combination (the service is not offered by the server). I stuck in the preparing state for quite a long time. After that I get to the waiting stage (with error message POSIXErrorCode: Operation timed out).
Is there any way to set the timeout for this first connection process ?
Thanks for your ideas
You can set connection timed out by NWProtocolTCP.Options
lazy var tcpOptions: NWProtocolTCP.Options = {
let options = NWProtocolTCP.Options()
options.connectionTimeout = 5 // connection timed out
return options
}()
lazy var parames: NWParameters = {
let parames = NWParameters(tls: nil, tcp: self.tcpOptions)
if let isOption = parames.defaultProtocolStack.internetProtocol as? NWProtocolIP.Options {
isOption.version = .v4
}
parames.preferNoProxies = true
return parames
}()
lazy var connection: NWConnection = {
let connection = NWConnection(host: "x.x.x.x", port: xxxx, using: self.parames)
return connection
}()
When trigger the timed out event, the .waiting state will call back.
2021-08-30 23:05:17.xxxxxx+xxxx Demo[xxxx:xxxxxx] [connection] nw_socket_handle_socket_event [C1:1] Socket SO_ERROR [60: Operation timed out]
The connection is waiting for a network path change with: POSIXErrorCode: Operation timed out
reference from Developer Fourms

Request not firing when returning to app from background

I'm returning to my app which is in the background from a successful payment from another app. When returning, a patch request should be fired to change status from available to sold. The problem is that the request is never fired.
I use alamofire session manager to make the request. And the function making the request is used all over the project and it works. It is also not a server error or a threading error as it runs on the main thread and the server works through curl / swagger.
It seems to me that the app does not have internet connection or something of the sort when returning to the app. Loggs can be found down below. The request has been succesfully fired a couple of times, which makes be believe in the internet connection theory.
Here are some selected parts of the code.
lazy var manager: SessionManager = {
let sessionManager = Alamofire.SessionManager.default
sessionManager.session.configuration.timeoutIntervalForRequest = 40
sessionManager.adapter = OauthHandler.sharedInstance
sessionManager.retrier = OauthHandler.sharedInstance
return sessionManager
}()
func authenticateRequestWithRouter(_ router: BaseRouter, shouldShowErrorAlert: Bool = true, _ completion: requestResponse?) {
let request = router.asURLRequest()
manager.request(request).validate().responseJSON() { [weak self] response in
switch response.result {
case .success(let data):
completion?(true, data, nil)
case .failure(let error):
if shouldShowErrorAlert,
let errorMessage = self?.parseErrorMessage(fromData: response.data, request: request) {
self?.showErrorAlert(message: errorMessage)
}
completion?(false, response.data, error)
}
}
}
Logs that have been occuring:
Returning ENOTCONN because protocol has not yet been set up
nw_connection_copy_connected_local_endpoint [C7] Connection has no
local endpoint
HTTP load failed (error code: -1005 [1:57])
Domain=NSURLErrorDomain Code=-1005 "The network connection was lost."
Domain=NSPOSIXErrorDomain Code=53 "Software caused connection abort"
nw_protocol_boringssl_error(1584) [C1.1:2][0x102838b30] Lower protocol
stack error: 53

Swift Socket, listening to incoming message

I am trying to implement swift socket in my application. I am pretty new to socket programming. I was able to send messages through the socket. But I am unable to listen to any messages from server. I have implemented the following code:
let host = Urls.GetServerIP()
let port = portNo
client = TCPClient(address: host, port: Int32(port))
switch client?.connect(timeout: 10) {
case .success?:
print((readResponse(from: client!)))
case .failure(let error)?:
print( String(describing: error))
break
case .none: break
}
In the above code, I am able to read an incoming message which comes as soon as I connect. But, I am unable to find a way to know that there is a message from the server, after couple of minutes.
Are there any specific call backs to know when a new message arrives from the server?
I have implemented StreamDelegate to my class. But no call backs are being triggered.
Thanks
It seems that your code lacks continuous reading, which is the reason that you only get a message once. I used to use SwiftSocket myself some time ago. The problem with this framework is that it is too simple and lacks a lot of functionality, for example encryption.
I switched to CocoaAsyncSocket, which is the Swiss army knife to handle socket connections in Swift.
Here is a short Example:
import CocoaAsyncSocket
class TcpSocketConnection: GCDAsyncSocketDelegate {
let tcpSocket: GCDAsyncSocket?
init(host: String, port: UInt16) {
self.tcpSocket = GCDAsyncSocket(delegate: self)
do {
try tcpSocket?.connect(toHost: host, onPort: port, withTimeout: 5.0)
} catch let error {
print("Cannot open socket to \(host):\(port): \(error)")
self.tcpSocket = nil
}
}
func socket(_ sock: GCDAsyncSocket, didConnectToHost host: String, port: UInt16) {
self.tcpSocket?.readData(toLength: 1024, withTimeout: 60.0)
}
func socket(_ sock: GCDAsyncSocket, didRead data: Data, withTag tag: Int) {
// Process data
self.tcpSocket?.readData(toLength: 1024, withTimeout: 60.0, tag: 0)
}
}
The code highly depends from how your server behaves and what you exactly want to do. Socket handling is not an easy task, especially when you add encryption (which you should do in modern applications). You can use the documentation as a starting point but you will have to read and learn a lot.
You can use SocketIOClient for web sockets. It can be used to send or receive messages through sockets.
Link:
https://github.com/nuclearace/Socket.IO-Client-Swift

Checking for internet connection or wifi connection with reachabilitySwift

I'm building an iOS app, at some point i needed to check the app's acces to internet, so i used the ReachabilitySwift library.
After some tests, it seems to me that the library only checks if the device has the wifi connected and not having an actual internet connection provided from my router.
lets say i disconnected my router from internet, if my iOS device is connected via wifi to the router, the reachability tells me that we have internet connection where in actuallity my router has no internet.
I tried the reachability with a particular host but still having the same result
var reachability = Reachability.init(hostname: "google.com");
Is the reachability library supposed to give feedback when the wifi connection is lost or the actual internet connection is lost ?
Thank you in advance.
I have had similar issues with Reachability where I was making web service calls to VPN protected network.
var reachability = Reachability.init(hostname: "google.com");
didnt work for me as it returned true when there is Wifi connection.
I used Alamofire response when a dummy call is made to the server
func networkIsAvailable(available: (Bool, String?) -> Void) {
var message : String = ""
DispatchQueue.main.async {
HUD.show(.progress)
Alamofire.request(Constants.baseUrl, method: .get, parameters: ["foo": "bar"])
.responseJSON(completionHandler: { (response) in
let error = response.result.error as? NSError
if error?.localizedDescription == networkAlert {
message = networkAlert
available(false, message)
} else if error?.code == cannotConnectServerCode || error?.localizedDescription == cannotConnectServerMessage {
message = cannotConnectServerMessage
available(false, anotherNetworkAlert)
} else {
available(true, nil)
}
})
}
}
At WWDC, Apple has said many many times that if you need to simply see if WiFi or cell is up - use Reachable. If you need to see if you can connect to - then don’t use Reachable; instead simply connect and handle the error.
Reachable doesn’t actually check to see if it can connect to that IP based resource and if you are going to get a result back. The only way to do that is to actually try.
Look for networking sessions with Quinn on Apple’s WWDC site to see the same advice.
In the Reachablility.h you can find a declaration:
typedef enum : NSInteger {
NotReachable = 0,
ReachableViaWiFi,
ReachableViaWWAN
} NetworkStatus;
Then, inside Reachability class you can find the method, which returns type:
- (NetworkStatus)currentReachabilityStatus;
So, use it. In your swift code you can do it like this:
if reachability.currentReachabilityStatus == .ReachableViaWiFi {
// Do things...
}

Resources