I try to do a client-server application with on the client side a Angular2/typescript web site and on the server side a Kitura server in Swift on Mac OSX.
On the client side, the typescript code instanciates an EventSource object :
this.eventSource = new EventSource(this.webSocketServerUrl);
this.eventSource.onopen = (event: Event): any => {
console.log("ServerNotificationsService.onopen - " + JSON.stringify(event) + " " + this.eventSource.readyState);
event.stopPropagation();
return null;
}
this.eventSource.onerror = (event: sse.IOnMessageEvent) => {
console.log("ServerNotificationsService.onerror - " + JSON.stringify(event) + " " + this.eventSource.readyState);
}
this.eventSource.onmessage = (event: Event): any => {
console.log("ServerNotificationsService.onmessage - " + JSON.stringify(event) + " " + this.eventSource.readyState);
event.stopPropagation();
return null;
}
console.log("ServerNotificationsService.constructor - " + this.eventSource.readyState);
On the server side, my code to handle the GET request looks like this :
router.get("/notifications") { request, response, next in
response.headers.append("Access-Control-Allow-Origin", value: "*")
if((request.accepts(header: "Accept", type: "text/event-stream")) != nil)
{
response.headers.append("content-type", value: "text/event-stream; charset=utf-8")
response.headers.append("cache-control", value: "no-cache")
response.headers.append("connection", value: "keep-alive")
try response.end()
Logger.sharedInstance.verbose(msg: "Request GET /notifications OK")
}
else
{
try response.status(.internalServerError).end()
Logger.sharedInstance.verbose(msg: "Request GET /notifications internalServerError")
}
next()
}
and to handle the post request :
router.post("/notifications") { request, response, next in
Logger.sharedInstance.verbose(msg: "Request POST /notifications ...")
response.headers.append("Access-Control-Allow-Origin", value: "*")
response.headers.append("content-type", value: "text/event-stream; charset=utf-8")
response.headers.append("cache-control", value: "no-cache")
response.headers.append("connection", value: "keep-alive")
while (true)
{
// wait here 5s. for the <nextMessage>
response.send(<nextMessage>)
try response.end()
Logger.sharedInstance.verbose(msg: "Request POST /notifications OK")
next()
}
}
The problem is that on the client side I receive the onopen notification, the event source's readyState pass to "Open" (1) but I receive immediately after a onerror notification and the readyState pass to "Connecting" (0). And so on : connecting, close, connecting, close, ... And in consequence the post request is never executed.
I will appreciate some help to have a code that maintains an open connexion.
Thank you,
Notux
Kitura does not currently support persistent, open HTTP connections. However, you might be able to replicate the functionality using WebSocket instead of server-sent events over HTTP (you will need to rewrite your frontend code to use WebSockets instead of EventSource):
https://github.com/IBM-Swift/Kitura-WebSocket
And example Kitura-WebSocket app can be found here:
https://github.com/IBM-Swift/Kitura-Chat-Server
Related
This is the approach I tried but not working. I can forward the incoming messages from the WebSocket connection to the NetSocket, but only the first one received by NetSocket arrives to the client behind the WebSocket.
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
const NetSocket = require('net');
const net = new NetSocket.Socket();
// Web socket
wss.on('connection', function connection(ws) {
console.log((new Date()) + ' Remote connection accepted ' + ws.remoteAddress);
ws.on('message', function incoming(message) {
console.log('Received from remote: %s', message);
net.write(message)
});
ws.on('close', function(){
console.log((new Date()) + ' Remote connection closed');
});
});
// Net socket
net.connect(8745, '127.0.0.1', function() {
console.log((new Date()) + ' Local connection accepted');
});
net.on('data', function(data) {
console.log('Received from local: ' + data);
// Iterate the connected devices to send the broadcast
wss.clients.forEach(function each(c) {
if (c.readyState === WebSocket.OPEN) {
c.send(data);
}
});
});
net.on('close', function() {
console.log('Local connection closed');
});
After a new research I noticed that the problem was in my swift code.
private func setReceiveHandler() {
webSocketTask.receive { result in
defer { self.setReceiveHandler() } // I was missing this line
do {
let message = try result.get()
switch message {
case let .string(text):
print("Received text message: \(text)")
case let .data(data):
So, just adding defer { self.setReceiveHandler() } to my function, it started to work.
Note the defer statement at the start of the receive handler. It calls self.setReceiveHandler() to reset the receive handler on the socket connection to allow it to receive the next message. Currently, the receive handler you set on a socket connection is only called once, rather than every time a message is received. By using a defer statement, you make sure that self.setReceiveHandler is always called before exiting the scope of the receive handler, which makes sure that you always receive the next message from your socket connection.
I've got the information from:
https://www.donnywals.com/real-time-data-exchange-using-web-sockets-in-ios-13/
i have the following akka http rejection handling code taken from https://doc.akka.io/docs/akka-http/current/routing-dsl/rejections.html
val message = "The requested resource could not be found."
implicit def myRejectionHandler = RejectionHandler.newBuilder()
.handleNotFound {
complete(HttpResponse(NotFound
,entity = HttpEntity(ContentTypes.`application/json`, s"""{"rejection": "$message"}"""
)))
}.result()
val route: Route = handleRejections(myRejectionHandler) {
handleExceptions(myExceptionHandler) {
concat(
path("event-by-id") {
get {
parameters('id.as[String]) {
id =>
complete("id")
}
}
}
,
post {
path("create-event") {
entity(as[Event]) {
event =>
complete(OK, "inserted")
}
}
}
)
}
}
}
val bindingFuture = Http().bindAndHandle(route, hostName, port)
when i hit localhost:8080/random
i got the message
HTTP method not allowed, supported methods: POST
and when i select POST and hit localhost:8080/random
i got the message
{
"rejection": "The requested resource could not be found."
}
why i did not get the same message when my route request was GET ?
in the docs the handleNotFound was working with GET request https://doc.akka.io/docs/akka-http/current/routing-dsl/rejections.html
This is happens, probably because of order of directives, you are using: in your configuration if incoming request does not match with event-by-id URL path, then it goes to the next handler, which expects that request should have POST method first of all, because post directive goes first, before path("create-event").
What you can try to do is change directives order to the next one, for second route:
path("create-event") {
post {
entity(as[Event]) { event =>
complete(OK, "inserted")
}
}
}
Hope this helps!
I have implemented the new MFP 8 Beta security concept. The positive case, with valid credentials is working fine and the processSuccess method that I have defined is executed.
Unfortunately, the negative case doesn’t work.
After calling the WLAuthorizationManager.login("scope"), I am getting a 401 in the console:
2016-05-20 13:48:41.965 Inspector[98311:1660747] [DEBUG] [WL_AFHTTPSessionManagerWrapper_PACKAGE] -[WLAFHTTPSessionManagerWrapper start] in WLAFHTTPSessionManagerWrapper.m:376 :: Starting the request with URL http://172.20.10.4:9080/mfp/api/preauth/v1/preauthorize
2016-05-20 13:48:41.983 Inspector[98311:1655477] [DEBUG] [WL_AFHTTPSessionManagerWrapper_PACKAGE] -[WLAFHTTPSessionManagerWrapper requestFailed:responseObject:error:] in WLAFHTTPSessionManagerWrapper.m:419 :: Request Failed
2016-05-20 13:48:41.984 Inspector[98311:1655477] [DEBUG] [WL_AFHTTPSessionManagerWrapper_PACKAGE] -[WLAFHTTPSessionManagerWrapper requestFailed:responseObject:error:] in WLAFHTTPSessionManagerWrapper.m:422 :: Response Status Code : 401
2016-05-20 13:48:41.984 Inspector[98311:1655477] [DEBUG] [WL_AFHTTPSessionManagerWrapper_PACKAGE] -[WLAFHTTPSessionManagerWrapper requestFailed:responseObject:error:] in WLAFHTTPSessionManagerWrapper.m:424 :: Response Error : Request failed: unauthorized (401)
Here is my implementation:
WLAuthorizationManager.login("UserLogin",{
'username':$scope.username,
'password':$scope.password
}).then( function () {
console.log(">> WLAuthorizationManager.login - onSuccess");
$scope.getInspectorDetails().then(
function(){
$scope.loginInProgress = false;
$state.go("inspectionList");
}
);
},
function (response) {
console.log(">> WLAuthorizationManager.login - onFailure: " + JSON.stringify(response));
$scope.loginInProgress = false;
if (!$scope.loginError){
$scope.loginError = "Could not connect to server. Please try again later.";
}
$scope.$apply();
});
}
And the Challenge handler:
$scope.registerChallengeHandler = function(){
console.log(">> in $scope.registerChllangeHandler ... ");
$scope.userLoginChallengeHandler = WL.Client.createWLChallengeHandler($scope.securityCheckName);
$scope.userLoginChallengeHandler.securityCheckName = $scope.securityCheckName;
$scope.userLoginChallengeHandler.handleChallenge = function(challenge) {
console.log(">> in UserLoginChallengeHandler - userLoginChallengeHandler.handleChallenge ...");
// When a session has expired, this will be our entry point into automatically logging back in
// (since the next server call the user tries to make will end up being flagged as a 'custom response'
// which will trigger the challenge hander. Thus, we need to turn on the progress spinner...
$scope.$apply(function(){
$scope.loginInProgress = true;
});
//show the login ...
$scope.user = { username: "", password: ""};
$scope.currentPath = $location.path();
console.log(">> $location.path(): " + $location.path());
if (!$state.is("login")){
$state.go("login");
}
$scope.isChallenged = true;
var statusMsg = "Remaining Attempts: " + challenge.remainingAttempts;
if (challenge.errorMsg !== null){
statusMsg = statusMsg + "<br/>" + challenge.errorMsg;
$timeout(function(){
//want to show only when submit user/pass not when token expired ...
if($scope.currentPath == "/"){
$scope.loginError = statusMsg;
}
}, 300);
}
console.log(">>> statusMsg : " + statusMsg);
};
$scope.userLoginChallengeHandler.processSuccess = function(data) {
console.log(">> in UserLoginChallengeHandler - userLoginChallengeHandler.processSuccess ...");
$scope.isChallenged = false;
$timeout(function(){
$scope.user = { username: "", password: ""};
}, 200);
$state.transitionTo("inspectionList");
};
$scope.userLoginChallengeHandler.handleFailure = function(error) {
console.log(">> in UserLoginChallengeHandler - userLoginChallengeHandler.handleFailure ...");
console.log(">> handleFailure: " + error.failure);
$scope.isChallenged = false;
if (error.failure !== null){
alert(error.failure);
} else {
alert("Failed to login.");
}
};
}
I would have expected that the handleFailure Method is called, but in the debugger I saw that it is not being executed. After the call of WLAuthorizationManager it just stops, so even the WLAuthorizationManager.login – onFailure is not called.
Edit:
Captured the traffic with Wireshark: https://ibm.box.com/s/7mtwsgea06i4bpdbdz0wvyhy3wpma58r
When using WLAuthorizationManager.login() with wrong credentials, the normal flow is that the challenge handler's handleChallenge will be called, to allow the user to try again.
In some cases, the security check might send a failure, such as "maximum attempt reached". In this case, the challenge handler's handleFailure is called.
WLAuthorizationManager.login() has its own failure scenarios. For example, let's say your server is down, there is no network, the security check does not exist, etc. In those cases, since there is no challenge involved, the login's failure will be called. That's when your then promise will come in handy.
Been trying to write a pebble app for wemo switches, currently this is the code i'm using:
function WemoRequest(callback) {
if (SOAPData === false || SOAPData === undefined) {
console.log("Invalid SOAP data: " + JSON.stringify(SOAPData));
return;
}
var url = "http://192.168.1.230:49153/upnp/control/basicevent1";
try {
var request = new XMLHttpRequest();
request.open("POST", url, false);
request.setRequestHeader("SOAPAction", "urn:Belkin:service:basicevent:1#GetBinaryState");
request.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
request.onreadystatechange = function() {
if (request.readyState == 4 && request.status === 200 && callback) {
callback(request, SOAPData);
}else{console.log("Status: "+request.status + " State: "+request.readyState+" Callback: "+callback);}
};
var packet = '<?xml version="1.0" encoding="utf-8"?>'+
'<s:Envelope xmls:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">'+
'<s:Body>'+
'<u:GetBinaryState xmlns:u="urn:Belkin:service:basicevent:1"></u:GetBinaryState>'+
'</s:Body>'+
'</s:Envelope>';
request.send(packet);
} catch (error) {
console.log("Error in XMLHttpRequest: " + error);
}}
I currently get status 500 from OnReadyStateChange and have no idea what I'm doing wrong. If this isn't enough code, app code is available here:https://github.com/dmf444/Webble
So...I know this is from 4 years ago lol, but I found this during a google search and just found the answer, so I figured I would respond for that reason: I think your header just needs an extra set of quotes around "urn:Belkin:service:basicevent:1#SetBinaryState" so that the string specifying the soapaction literally starts and ends with quotes.
I'm working in Python (because that's what all the kids seem to be doing these days), but I too was getting the 500 error until I made a very subtle change (the single quote marks around my double quotes) and almost cried tears of joy when my light turned off:
"SOAPACTION": '"urn:Belkin:service:basicevent:1#SetBinaryState"'
So here's the working version of the code (in Python lol):
import http.client
#Variables (value=on/off, ipaddress=address of your wemo)
value = 0 #1=ON, 0=OFF
ipAddress = "192.168.0.108"
#Build the SOAP Envelope (data)
data = '<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:SetBinaryState xmlns:u="urn:Belkin:service:basicevent:1"><BinaryState>' + str(value) + '</BinaryState></u:SetBinaryState></s:Body></s:Envelope>'
#Build the Header (headers)
headers = {"Content-type" : 'text/xml; charset="utf-8"', "SOAPACTION": '"urn:Belkin:service:basicevent:1#SetBinaryState"', "Content-Length": len(data)}
#Send request and check response data (resp_data)
conn = http.client.HTTPConnection(ipAddress, 49153)
conn.request("POST", "/upnp/control/basicevent1", data, headers)
response = conn.getresponse()
resp_data = response.read()
if response.status == 200:
conn.close()
print("SUCCESS!")
elif response.status == 403:
print("ERROR: 403 (FORBIDDEN)")
else:
print("ERROR: " + str(response.status))
I a using scala.js (0.6.5) and scala-js-dom (0.8.2) and I have some strange pb with an ajax.post, when I receive an error status (409 here).
The browser console shows an error message, but from my scala code I cannot have access to the status code, and to the message returned.
Here is the code I use for sending a POST:
val request = Ajax.post(
url,
data = postData,
headers = bsHeaders)
request.map(xhr => {
log.debug("response text: " + xhr.responseText)
if (xhr.status == 201) {
try {
val loc = xhr.getResponseHeader("Location")
if(loc == locHeaderResp) {
loc
} else {
log.error(s"Location header invalid: ${loc}")
}
} catch {
case e:Exception => {
log.error("Couldn't read 'Location' header " + e.getMessage)
log.debug("List of headers: " + xhr.getAllResponseHeaders())
""
}
}
} else if (xhr.status == 409) {
log.error("" + xhr.responseText)
log.error(s"${xhr.responseText}")
} else {
log.error(s"Request failed with response code ${xhr.status}")
log.error(s"${xhr.responseText}")
}
})
When the status is 201, it works well.
In my case, when the data I am sending already exists, I am supposed to get a 409 error code, with some message status. And from the browser debugging tools it is indeed the case.
I was expecting to be able to manage error case when doing the 'request.map', but when an error code is returned, this code is not executed.
So how to manage errors with POST messages?
This is expected. Ajax.post returns a Future, and the map method of Futures are only executed for the successful cases. A return code of 409 is considered a failure, and will therefore complete the future with a failed status.
To handle failures with Futures, you should use their onFailure method:
request.map(req => {
// ... handle success cases (req.status is 2xx or 304)
}).onFailure {
case dom.ext.AjaxException(req) =>
// ... handle failure cases (other return codes)
})
If you would rather deal with failure return codes in the same code as success return codes, you can first recover to turn a failed AjaxException(req) into a successful req:
request.recover {
// Recover from a failed error code into a successful future
case dom.ext.AjaxException(req) => req
}.map(req => {
// handle all status codes
}