I'm a beginner using tornado and WebSocket. I'm trying to write a websocket demo with tornado to send and receive messages. However, when the client was connected to the server and was waiting to receiving messages from the server, a close event occurred with 1006 code which isbelow.
CloseEvent {isTrusted: true, wasClean: false, code: 1006, reason: '',
type: 'close', …} bubbles: false cancelBubble: false cancelable: false
code: 1006 composed: false currentTarget: WebSocket {url:
'ws://localhost:9998/ws', readyState: 3, bufferedAmount: 0, onopen:
null, onerror: null, …} defaultPrevented: false eventPhase: 0
isTrusted: true path: [] reason: "" returnValue: true srcElement:
WebSocket {url: 'ws://localhost:9998/ws', readyState: 3,
bufferedAmount: 0, onopen: null, onerror: null, …} target: WebSocket
{url: 'ws://localhost:9998/ws', readyState: 3, bufferedAmount: 0,
onopen: null, onerror: null, …} timeStamp: 75840 type: "close"
wasClean: false [[Prototype]]: CloseEvent
When I'm trying to debug where caused the close event, I found that the code in the
tornado/web.py
caused the close event.
When this code self.request.connection.finish() was executed ,the close event occurred. I don't know why this code can cause close event.
future = self.flush(include_footers=True)
self.request.connection.finish()
self._log()
self._finished = True
self.on_finish()
self._break_cycles()
return future
Here is my demo code, is there anything wrong with my code that cause the close event?
The sever code:
import tornado.ioloop
import tornado.web
import tornado.websocket
import time
class ConnectHandler(tornado.websocket.WebSocketHandler) :
def simple_init(self):
self.last = time.time();
self.stop = False;
def check_origin(self, origin) :
return True
def open(self) :
self.simple_init();
print("New client connecting")
self.write_message('Welcome')
print("New client connected")
def on_close(self) :
print("connection is closed")
self.loop.stop();
def on_message(self, message) :
self.write_message('new message :' + message)
class Application(tornado.web.Application) :
def __init__(self) :
handlers = [
(r'/ws', ConnectHandler)
]
tornado.web.Application.__init__(self, handlers)
if __name__ == "__main__" :
app = Application()
app.listen(8889)
tornado.ioloop.IOLoop.current().start()
The client code:
from websocket import create_connection
def short_lived_connection():
ws = create_connection("ws://localhost:9998/ws");
print("send hello server");
ws.send("hello, server");
print("sent");
print("receiving...");
result = ws.recv();
print("Received '%s'" % result);
ws.close();
# Press the green button in the gutter to run the script.
if __name__ == '__main__':
short_lived_connection()
Related
Currently, I'm using a Lambda function, SSM and Aurora DB together.
I manage to get Aurora's credentials from SSM and the whole lambda is completed but I can't query the database. I took this tutorial as a reference which was also recommended by the aws support yet I'm still unable to use the Aurora Database
The following is my lambda code:
export async function handler (event:any)
{
try
{
console.log("Welcome to lambda")
const ssm = new SSM();
const username = await ssm
.getParameter({ Name:*** , WithDecryption: false })
.promise();
const password = await ssm
.getParameter({ Name:*** , WithDecryption: true })
.promise();
console.log("Before Connection")
let pool = mysql.createPool({
host: '***',
database: 'aurora-test1-us-mysql-multilevel',
user: username.Parameter?.Value,
password: password.Parameter?.Value,
port:3306
});
pool.getConnection(function(err, connection) {
// Use the connection
connection.query('SELECT * from feature', function (error, results, fields) {
console.log("result",results[0]);
// And done with the connection.
connection.release();
// Handle error after the release.
if (error)
console.log("Error: ",error)
else
console.log("result",results[0].emp_name);
});
});
let q = pool.query('SELECT * from feature')
console.log("Query",q)
return {
statusCode: 200,
body: "HELLO KIMOO!!! Welcome TO AURORA DB" + "Database Created2"
}
}
catch(err)
{
console.log("Error caught",err)
return {
statusCode: 500,
body: JSON.stringify({
message: 'Error: ' + err
})
}
}
}
The following is the the output for console.log("Query",q):
Query <ref *1> Query {
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
_callback: undefined,
_callSite: Error
at Pool.query (/var/task/node_modules/mysql/lib/Pool.js:199:23)
at Runtime.<anonymous> (/var/task/index.js:48:26)
at Generator.next (<anonymous>)
at fulfilled (/var/task/index.js:5:58)
at processTicksAndRejections (node:internal/process/task_queues:96:5),
_ended: false,
_timeout: undefined,
_timer: Timer { _object: [Circular *1], _timeout: null },
sql: 'SELECT * from feature',
values: undefined,
typeCast: true,
nestTables: false,
_resultSet: null,
_results: [],
_fields: [],
_index: 0,
_loadError: null,
[Symbol(kCapture)]: false
}
NOTE:
There are no logs for console.log("result",results[0]);
I'm using graphql-ws https://www.npmjs.com/package/graphql-ws to manage my websocket connection, but am unable to figure out how to handle a dropped connection. Once my internet drops (toggling wifi) or computer sleeps, subscriptions all drop and websocket never reconnects.
closed never gets called. Everything else works as expected, just the disconnects an issue.
createClient({
retryAttempts: 5,
shouldRetry: () => true,
url: "ws://localhost:8080",
on: {
connected: () => {
console.log("CONNECTED");
},
closed: () => {
console.log("CLOSED");
},
error: (e) => {
console.log(e);
},
},
})
);
You can use keepAlive, ping, and pong as a trigger to restart your connection, and keep retryAttempt to infinite.
That's my attempt at keeping the socket alive:
createClient({
url: 'wss://$domain/v1/graphql',
retryAttempts: Infinity,
shouldRetry: () => true,
keepAlive: 10000,
connectionParams: () => {
const access_token = getAccessTokenFunction();
return {
headers: {
Authorization: `Bearer ${access_token || ''}`
}
};
},
on: {
connected: (socket) => {
activeSocket = socket; // to be used at pings & pongs
// get the access token expiry time and set a timer to close the socket
// once the token expires... Since 'retryAttempts: Infinity' it will
// try to reconnect again by getting a fresh token.
const token_expiry_time = getTokenExpiryDate();
const current_time = Math?.round(+new Date() / 1000);
const difference_time = (token_expiry_time - current_time) * 1000;
if (difference_time > 0) {
setTimeout(() => {
if (socket?.readyState === WebSocket?.OPEN) {
socket?.close(CloseCode?.Forbidden, "Forbidden");
}
}, difference_time);
}
},
ping: (received) => {
if (!received)
// sent
timedOut = setTimeout(() => {
if (activeSocket?.readyState === WebSocket?.OPEN)
activeSocket?.close(4408, 'Request Timeout');
}, 5000); // wait 5 seconds for the pong and then close the connection
},
pong: (received) => {
if (received) clearTimeout(timedOut); // pong is received, clear connection close timeout
}
}
})
The "res" value is an object, that is not retrieving the data related to the selector, is working in other places, but in the effect is getting this object. Why is happening this?
constructor(
private serviceStore: Store<DataState>,
) {
searchForLatest$ = createEffect(() =>
this._actions.pipe(
ofType<GetLatestRequestService>(GetLatestData),
withLatestFrom(({ id }) =>
this.serviceStore.select(getlatestData(id)),
mergeMap(res => {
actionsObserver: {
closed: false,
hasError: false,
isStopped: false,
observers: [SkipSubscriber],
thrownError: null,
_isScalar: false,
}
operator: {
compare: undefined
keySelector: undefined
}
reducerManager: {
closed: false
dispatcher: DevtoolsDispatcher {_isScalar: false, observers: Array(1), closed: false,
isStopped: false, hasError: false, …}
hasError: false
initialState: undefined
isStopped: false
observers: [MapSubscriber]
reducerFactory: (reducers, initialState) => {…}
reducers: {uiContext: ƒ, parties: ƒ, user: ƒ, organizationsDetail: ƒ, activeRoute: ƒ, …}
thrownError: null
_isScalar: false
_value: (state, action) =>
}
Source: {
actionsObserver: ActionsSubject {_isScalar: false, observers: Array(1), closed: false,
isStopped: false, hasError: false, …}
operator: MapOperator {thisArg: undefined, project: ƒ}
reducerManager: ReducerManager {_isScalar: false, observers: Array(1), closed: false,
isStopped: false, hasError: false, …}
source: Store {_isScalar: false, actionsObserver: ActionsSubject, reducerManager:
ReducerManager, source: Observable}
_isScalar: false
}
_isScalar: false
The effects in v13 updated the approach to retrieve the latest data from a selector, I need to use the concatLatestFrom operator to get the data.
#Injectable()
export class CollectionEffects {
addBookToCollectionSuccess$ = createEffect(
() =>
this.actions$.pipe(
ofType(CollectionApiActions.addBookSuccess),
concatLatestFrom(action => this.store.select(fromBooks.getCollectionBookIds)),
tap(([action, bookCollection]) => {
if (bookCollection.length === 1) {
window.alert('Congrats on adding your first book!');
} else {
window.alert('You have added book number ' + bookCollection.length);
}
})
),
{ dispatch: false }
);
constructor(
private actions$: Actions,
private store: Store<fromBooks.State>
) {}
}
Note: For performance reasons, use a flattening operator like concatLatestFrom to prevent the selector from firing until the correct action is dispatched.
I have been running a sample RSocket project from (https://dzone.com/articles/rsocket-with-spring-boot-amp-js-zero-to-hero) where I was trying to store requestor Object per client Id.
Rsocket JS Client :
client = new RSocketClient({
serializers: {
data: JsonSerializer,
metadata: IdentitySerializer
},
setup: {
payload: {
data: "121212",
metadata: String.fromCharCode("client-id".length) + "client-id"
},
keepAlive: 60000,
lifetime: 180000,
dataMimeType: "application/json",
metadataMimeType: "message/x.rsocket.routing.v0"
},
transport: new RSocketWebSocketClient({
url: 'ws://localhost:8080/tweetsocket'
}),
});
// Open the connection
client.connect().subscribe({
onComplete: socket => {
// socket provides the rsocket interactions fire/forget, request/response,
// request/stream, etc as well as methods to close the socket.
socket.requestStream({
data: {
'author': document.getElementById("author-filter").value
},
metadata: String.fromCharCode('tweets.by.author'.length) + 'tweets.by.author',
}).subscribe({
onComplete: () => console.log('complete'),
onError: error => {
console.log(error);
addErrorMessage("Connection has been closed due to ", error);
},
onNext: payload => {
console.log(payload.data);
reloadMessages(payload.data);
},
onSubscribe: subscription => {
subscription.request(2147483647);
},
});
},
onError: error => {
console.log(error);
addErrorMessage("Connection has been refused due to ", error);
},
onSubscribe: cancel => {
/* call cancel() to abort */
}
});
}
Rsocket Java Server :
#ConnectMapping(value = "client-id")
public void onConnect(RSocketRequester rSocketRequester, #Payload String clientId) {
logger.info(clientId);
rSocketRequester.rsocket()
.onClose() // (1)
// .doFirst(() -> logger.info("Client Connected "))
// .doOnError( error -> logger.info("Channel to client {} CLOSED", error)) // (3)
.subscribe(null, null, () -> logger.info("herererer"));
}
#MessageMapping("tweets.by.author")
public Flux<Tweet> getByAuthor(TweetRequest request) {
return service.getByAuthor(request.getAuthor());
}
I have been running the application in debug mode and am unable to trigger onConnect ConnectMapping annotated method. Please help to understand what am I missing here.
The issue here is potentially a few things:
It's best to use composite metadata when defining a route
When using composite metadata, it's important to use IdentitySerializer for metadata
IdentitySerializer will pass the value of metadata through as-is, without any modification. This is important as the value for metadata is already encoded as it should be from encodeCompositeMetadata(...).
import {
BufferEncoders,
JsonSerializer,
RSocketClient,
APPLICATION_JSON,
MESSAGE_RSOCKET_COMPOSITE_METADATA,
encodeRoute, MESSAGE_RSOCKET_ROUTING, encodeCompositeMetadata, IdentitySerializer
} from "rsocket-core";
import RSocketWebSocketClient from "rsocket-websocket-client";
const client = new RSocketClient({
serializers: {
data: JsonSerializer,
metadata: IdentitySerializer
},
setup: {
payload: {
data: "121212",
metadata: encodeCompositeMetadata([
[MESSAGE_RSOCKET_ROUTING, encodeRoute("client-id")],
])
},
keepAlive: 60000,
lifetime: 180000,
dataMimeType: APPLICATION_JSON.string,
metadataMimeType: MESSAGE_RSOCKET_COMPOSITE_METADATA.string,
},
transport: new RSocketWebSocketClient({
url: 'ws://localhost:8080/tweetsocket'
}, BufferEncoders),
});
I programmed a small app (js) that get all the posts o a blog from the server (phoenix framework+PostgreSQL). The app is working, but at the nth call of the API, I get n replies instead of 1:
Joined successfully app.js:18747:10
client getAll call app.js:18698:8
client getAll reply, Object { posts: Array[3] } app.js:18694:10
client getAll call app.js:18698:8
client getAll reply, Object { posts: Array[3] } app.js:18694:10
client getAll reply, Object { posts: Array[3] } app.js:18694:10
client getAll call app.js:18698:8
client getAll reply, Object { posts: Array[3] } app.js:18694:10
client getAll reply, Object { posts: Array[3] } app.js:18694:10
client getAll reply, Object { posts: Array[3] }
That is: at the 3rd call I get 3 replied instead of 1.
Here the files: user_socket.ex:
defmodule Proto1.UserSocket do
use Phoenix.Socket, Phoenix.LongPoll
channel "blog", Proto1.BlogChannel
transport :websocket, Phoenix.Transports.WebSocket
transport :longpoll, Phoenix.Transports.LongPoll
def connect(_params, socket) do
{:ok, socket}
end
def id(_socket), do: nil
end
blog_channel.ex
defmodule Proto1.BlogChannel do
use Proto1.Web, :channel
def join("blog", _message, socket) do
{:ok, socket }
end
def handle_in("getAll", params, socket) do
IO.puts "Proto1.BlogChannel.handle_in \"all\" called"
posts = Repo.all(Proto1.Post)
push socket, "getAll", %{posts: for p <- posts do %{title: p.title, body: p.body} end }
{:noreply, socket}
end
end
And on the client (vue.js 2): The Endpoint:
defmodule Proto1.Endpoint do
use Phoenix.Endpoint, otp_app: :proto1
socket "/socket", Proto1.UserSocket
The socket:
import {Socket} from 'phoenix'
let socket = new Socket('/socket', {params: {token: window.userToken}})
socket.connect()
export default socket
Some code to manage the channel:
import socket from './socket'
class Blog {
// in the future the construcor will have a single parameter with the
// id of the blog, now we hava a single blog
constructor (blogId) {
this._blogId = blogId
this._channel = socket.channel('blog')
this.join()
this.initOn()
}
join () {
this._channel.join()
.receive('ok', resp => { console.log('Joined successfully') })
.receive('error', resp => { console.log('Unable to join') })
}
initOn () {
this._channel.on('all', payload => {
console.log('payload: ', payload)
})
}
getChannel () {
return this._channel
}
}
let BlogFactory = {
blogs: new Map(),
getBlog: function (blogId = 'default') {
if (this.blogs[blogId] === undefined) {
this.blogs[blogId] = new Blog(blogId)
}
return this.blogs[blogId]
}
}
export default BlogFactory
The data fetch:
[...]
methods: {
fetchData () {
this.error = this.posts = null
this.loading = true
var blog = BlogFactory.getBlog()
var c = blog.getChannel()
c.on('getAll', payload => {
console.log('client getAll reply, ', payload)
this.loading = false
this.posts = payload.posts
})
console.log('client getAll call')
c.push('getAll')
}
}
[...]
Changed client program after the answer of #Nicd (the following code works!):
<script>
// import BlogFactory from '../blog'
import socket from '../socket'
export default {
name: 'blog',
data () {
return {
loading: false,
posts: null,
error: null
}
},
created () {
this.channel = socket.channel('blog')
this.channel.join()
.receive('ok', resp => { console.log('Joined successfully') })
.receive('error', resp => { console.log('Unable to join') })
this.channel.on('getAll', payload => {
console.log('client getAll reply, ', payload)
this.loading = false
this.posts = payload.posts
})
this.fetchData()
},
methods: {
fetchData () {
this.error = this.posts = null
this.loading = true
console.log('client getAll call')
this.channel.push('getAll')
}
}
}
</script>
It seems that every time you call fetchData(), you are adding a new listener with the c.on('getAll', ...) call. So for every received message you run an increasing amount of listeners and that is why it looks like you received many messages.
You can check this with your browser's developer tools – at least Chromium based browsers allow you to inspect the WebSocket traffic to see that there is only one received message.