Channels websocket AsyncJsonWebsocketConsumer disconnect not reached - django

I have the following consumer:
class ChatConsumer(AsyncJsonWebsocketConsumer):
pusher = None
async def connect(self):
print(self.scope)
ip = self.scope['client'][0]
print(ip)
self.pusher = await self.get_pusher(ip)
print(self.pusher)
await self.accept()
async def disconnect(self, event):
print("closed connection")
print("Close code = ", event)
await self.close()
raise StopConsumer
async def receive_json(self, content):
#print(content)
if 'categoryfunctionname' in content:
await cellvoltage(self.pusher, content)
else:
print("ERROR: Wrong data packets send")
print(content)
#database_sync_to_async
def get_pusher(self, ip):
p = Pusher.objects.get(auto_id=1)
try:
p = Pusher.objects.get(ip=ip)
except ObjectDoesNotExist:
print("no pusher found")
finally:
return p
Connecting, receiving and even getting stuff async from the database works perfectly. Only disconnecting does not work as expected. The following Terminal Log explains what's going on:
2018-09-19 07:09:56,653 - INFO - server - HTTP/2 support not enabled (install the http2 and tls Twisted extras)
2018-09-19 07:09:56,653 - INFO - server - Configuring endpoint tcp:port=1111:interface=192.168.1.111
2018-09-19 07:09:56,653 - INFO - server - Listening on TCP address 192.168.1.111:1111
[2018/09/19 07:11:25] HTTP GET / 302 [0.02, 10.171.253.112:35236]
[2018/09/19 07:11:25] HTTP GET /login/?next=/ 200 [0.05, 10.111.253.112:35236]
{'type': 'websocket', 'path': '/ws/chat/RP1/', 'headers': [(b'upgrade', b'websocket'), (b'connection', b'Upgrade'), (b'host', b'10.111.111.112:1111'), (b'origin', b'http://10.111.253.112:1111'), (b'sec-websocket-key', b'vKFAnqaRMm84AGUCxbAm3g=='), (b'sec-websocket-version', b'13')], 'query_string': b'', 'client': ['10.111.253.112', 35238], 'server': ['192.168.1.111', 1111], 'subprotocols': [], 'cookies': {}, 'session': <django.utils.functional.LazyObject object at 0x7fe4a8d1ba20>, 'user': <django.utils.functional.LazyObject object at 0x7fe4a8d1b9e8>, 'path_remaining': '', 'url_route': {'args': (), 'kwargs': {'room_name': 'RP1'}}}
10.111.253.112
[2018/09/19 07:11:25] WebSocket HANDSHAKING /ws/chat/RP1/ [10.111.253.112:35238]
[2018/09/19 07:11:25] WebSocket CONNECT /ws/chat/RP1/ [10.111.111.112:35238]
no pusher found
1 - DEFAULT - 0.0.0.0
ERROR: Wrong data packets send
{'hello': 'Did I achieve my objective?'}
[2018/09/19 07:11:46] WebSocket DISCONNECT /ws/chat/RP1/ [10.111.253.112:35238]
2018-09-19 07:11:56,792 - WARNING - server - Application instance <Task pending coro=<SessionMiddlewareInstance.__call__() running at /home/pi/PycharmProjects/LOGGER/venv/lib/python3.6/site-packages/channels/sessions.py:175> wait_for=<Future pending cb=[_chain_future.<locals>._call_check_cancel() at /usr/lib/python3.6/asyncio/futures.py:403, <TaskWakeupMethWrapper object at 0x7fe4a82e6fd8>()]>> for connection <WebSocketProtocol client=['10.171.253.112', 35238] path=b'/ws/chat/RP1/'> took too long to shut down and was killed.
After the 10 seconds timeout it gives a warning that the connection was killed:
WARNING - server - Application instance taskname running at location
at linenumber for connection cxn-name took too long to shut down and
was killed.
The disconnect method was thus also not reached.
What could this be?
Am I using the correct method?
Could I expand the timeout period?

if you are intending to run some custom logic during connection close then you should override websocket_disconnect and then call super (rather than rais the exception yourself)
https://github.com/django/channels/blob/507cb54fcb36df63282dd19653ea743036e7d63c/channels/generic/websocket.py#L228-L241

The code linked in the other answer was very helpful. Here it is for reference (as of May 2022):
async def websocket_disconnect(self, message):
"""
Called when a WebSocket connection is closed. Base level so you don't
need to call super() all the time.
"""
try:
for group in self.groups:
await self.channel_layer.group_discard(group, self.channel_name)
except AttributeError:
raise InvalidChannelLayerError(
"BACKEND is unconfigured or doesn't support groups"
)
await self.disconnect(message["code"])
raise StopConsumer()
async def disconnect(self, code):
"""
Called when a WebSocket connection is closed.
"""
pass
I'd note that there is no need to use super() on websocket_disconnect. They have a hook disconnect which was perhaps added since the original answer that can be used to add any teardown code. You can simply override the disconnect method in your own class and it will be called.

Related

RabbitMQ finishes up all space in EC2 machine

I have a RabbitMQ single instance running on docker on an EC2 machine on AWS
It runs out of disk somehow, I don't quite understand how because I'm using it just to submit messages, and I haven't configured any persistence for them.
I've even added this to the config, but this was only a temporary solution:
loopback_users.guest = false
log.console = true
disk_free_limit.absolute = 2GB
After a few weeks work I've gotten this from my python client:
Connection <Connection: "amqp://admin:******#rabbitmq:5672/" at 0x7ff757125d00> was blocked by: 'low on disk'
My usage is like so:
Publisher:
url = config.RABBITMQ_URL
self.connection = await connect(url)
async with self.connection:
channel = await self.connection.channel()
exchange = await channel.declare_exchange("quotes", ExchangeType.FANOUT)
message_body = json.dumps(msg).encode()
message = Message(message_body, delivery_mode=DeliveryMode.NOT_PERSISTENT, expiration=300)
await exchange.publish(message, routing_key="quote")
logger.debug(f"Message sent: {message.body}")
Subscriber:
async def on_message(message: AbstractIncomingMessage):
async with message.process():
decoded_msg = message.body.decode()
quote: Quote = json.loads(decoded_msg)
strategy: Strategy
for strategy in strategies:
if strategy.symbol == quote["symbol"]:
await strategy(quote)
async def main():
connection = await connect(url=config.RABBITMQ_URL)
async with connection:
channel = await connection.channel()
await channel.set_qos(prefetch_count=1)
exchange = await channel.declare_exchange("quotes", ExchangeType.FANOUT)
queue = await channel.declare_queue(name="quotes")
await queue.bind(exchange, routing_key="quote")
await queue.consume(on_message)
print(" [x] Waiting for quotes. To exit press CTRL+C")
try:
await asyncio.Future()
finally:
await connection.close()
This is how i start up the RabbitMQ container:
services:
rabbitmq:
image: rabbitmq:3-management
container_name: rabbitmq
restart: always
ports:
- "5672:5672"
- "15672:15672"
environment:
RABBITMQ_DEFAULT_USER: ${RABBITMQ_USERNAME}
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD}
volumes:
- ./conf/:/etc/rabbitmq/conf.d/
healthcheck:
test: rabbitmq-diagnostics -q ping
interval: 30s
timeout: 30s
retries: 3
I use the aio_pika python library as the RabbitMQ client

Django channels redis channel layer opens a lot of connections

We ported a part of our application to django channels recently, using the redis channel layer backend. A part of our setup still runs on python2 in a docker which is why we use redis pub/sub to send messages back to the client. A global listener (inspired by this thread) catches all messages and distributes them into the django channels system. It all works fine so far but I see a lot of debug messages Creating tcp connection... passing by. The output posted below corresponds to one event. Both the listener as well as the consumer seem to be creating two redis connections. I have not enough knowledge about the underlying mechanism to be able to tell if this the expected behavior, thus me asking here. Is this to be expected?
The Listener uses a global channel layer instance:
# maps the publish type to a method name of the django channel consumer
PUBLISH_TYPE_EVENT_MAP = {
'state_change': 'update_client_state',
'message': 'notify_client',
}
channel_layer = layers.get_channel_layer()
class Command(BaseCommand):
help = u'Opens a connection to Redis and listens for messages, ' \
u'and then whenever it gets one, sends the message onto a channel ' \
u'in the Django channel system'
...
def broadcast_message(self, msg_body):
group_name = msg_body['subgrid_id'].replace(':', '_')
try:
event_name = PUBLISH_TYPE_EVENT_MAP[msg_body['publish_type']]
# backwards compatibility
except KeyError:
event_name = PUBLISH_TYPE_EVENT_MAP[msg_body['type']]
async_to_sync(channel_layer.group_send)(
group_name, {
"type": event_name,
"kwargs": msg_body.get('kwargs'),
})
The consumer is a JsonWebsocketConsumer that is initialized like this
class SimulationConsumer(JsonWebsocketConsumer):
def connect(self):
"""
Establishes the connection with the websocket.
"""
logger.debug('Incoming connection...')
# subgrid_id can be set dynamically, see property subgrid_id
self._subgrid_id = self.scope['url_route']['kwargs']['subgrid_id']
async_to_sync(self.channel_layer.group_add)(
self.group_name,
self.channel_name
)
self.accept()
And the method that is called from the listener:
def update_client_state(self, event):
"""
Public facing method that pushes the state of a simulation back
to the client(s). Has to be called through django channels
```async_to_sync(channel_layer.group_send)...``` method
"""
logger.debug('update_client_state event %s', event)
current_state = self.redis_controller.fetch_state()
data = {'sim_state': {
'sender_sessid': self.session_id,
'state_data': current_state}
}
self.send_json(content=data)
A single event gives me this output
listener_1 | DEBUG !! data {'publish_type': 'state_change', 'subgrid_id': 'subgrid:6d1624b07e1346d5907bbd72869c00e8'}
listener_1 | DEBUG !! event_name update_client_state
listener_1 | DEBUG !! kwargs None
listener_1 | DEBUG !! group_name subgrid_6d1624b07e1346d5907bbd72869c00e8
listener_1 | DEBUG Using selector: EpollSelector
listener_1 | DEBUG Parsing Redis URI 'redis://:#redis-threedi-server:6379/13'
listener_1 | DEBUG Creating tcp connection to ('redis-threedi-server', 6379)
listener_1 | DEBUG Parsing Redis URI 'redis://:#redis-threedi-server:6379/13'
listener_1 | DEBUG Creating tcp connection to ('redis-threedi-server', 6379)
threedi-server_1 | DEBUG Parsing Redis URI 'redis://:#redis-threedi-server:6379/13'
threedi-server_1 | DEBUG Creating tcp connection to ('redis-threedi-server', 6379)
threedi-server_1 | DEBUG update_client_state event {'type': 'update_client_state', 'kwargs': None}
threedi-server_1 | DEBUG Parsing Redis URI 'redis://:#redis-threedi-server:6379/13'
threedi-server_1 | DEBUG Creating tcp connection to ('redis-threedi-server', 6379)

Python Orion Context Broker Token problems

I've been developing the following code:
datos = {
"id":"1",
"type":"Car",
"bra":"0",
}
jsonData = json.dumps(datos)
url = 'http://130.456.456.555:1026/v2/entities'
head = {
"Content-Type": "application/json",
"Accept": "application/json",
"X-Auth-Token": token
}
response = requests.post(url, data=jsonData, headers=head)
My problem is that I can't establish a connection between my computer and my fiware Lab instance.
The error is:
requests.exceptions.ConnectionError: HTTPConnectionPool(host='130.206.113.177', port=1026): Max retries exceeded with url: /v1/entities (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f02c97c1f90>: Failed to establish a new connection: [Errno 110] Connection timed out',))
Seems to be a network connectivity problem.
Assuming that there actually an Orion process listening to port 1026 at IP 130.206.113.177 (should be checked, eg. curl localhost:1026/version command executed in the same VM where Orion runs), the most probable causes of Orion connection problems are:
Something in the Orion host (e.g a firewall or security group) is blocking the incoming connection
Something in the client host (e.g a firewall) is blocking the outcoming connection
There is some other network issue is causing the connection problem.

django channel set up routing

I'm learning how to setup a websocket connection with django-channel, i've got this setup in routing.py
from channels import route
from gallery import socket as sock
channel_routing = [
# Wire up websocket channels to our consumers:
route("websocket.connect", sock.ws_connect, path=r"^/render-status/$"),
route("websocket.receive", sock.ws_receive, , path=r"^/render-status/$"),
]
and the following javascript
// When we're using HTTPS, use WSS too.
var ws_scheme = window.location.protocol == "https:" ? "wss" : "ws";
var ws_path = ws_scheme + '://' + window.location.host + '/render-status/';
console.log("Connecting to " + ws_path)
var socket = new ReconnectingWebSocket(ws_path);
socket.onmessage = function(message) {
console.log("Got message: " + message.data);
var data = JSON.parse(message.data);
// if action is started, add new item to table
if (data.action == "started") {
}
// if action is completed, just update the status
else if (data.action == "completed"){
}
};
var message = {
action: "incomplete",
job_name: "10",
};
socket.send(JSON.stringify(message));
tried it out and there's a failure in connecting (from the console)
colorpicker2.js:565 Connecting to ws://127.0.0.1:8000/render-status/
reconnecting-websocket.min.js:1 Uncaught DOMException: Failed to execute 'send' on 'WebSocket': Still in CONNECTING state.
at a.send (http://127.0.0.1:8000/static/js/reconnecting-websocket.min.js:1:2611)
at HTMLDocument.<anonymous> (http://127.0.0.1:8000/static/js/colorpicker2.js:584:11)
at k (http://127.0.0.1:8000/static/js/jquery.js:15:16962)
at Object.fireWith [as resolveWith] (http://127.0.0.1:8000/static/js/jquery.js:15:17720)
at Function.ready (http://127.0.0.1:8000/static/js/jquery.js:15:12435)
at HTMLDocument.D (http://127.0.0.1:8000/static/js/jquery.js:15:9840)
send # reconnecting-websocket.min.js:1
(anonymous) # colorpicker2.js:584
k # jquery.js:15
fireWith # jquery.js:15
ready # jquery.js:15
D # jquery.js:15
reconnecting-websocket.min.js:1 WebSocket connection to 'ws://127.0.0.1:8000/render-status/' failed: Error during WebSocket handshake: Unexpected response code: 404
open # reconnecting-websocket.min.js:1
a # reconnecting-websocket.min.js:1
(anonymous) # colorpicker2.js:566
k # jquery.js:15
fireWith # jquery.js:15
ready # jquery.js:15
D # jquery.js:15
22reconnecting-websocket.min.js:1 WebSocket connection to 'ws://127.0.0.1:8000/render-status/' failed: Error during WebSocket handshake: Unexpected response code: 404
open # reconnecting-websocket.min.js:1
(anonymous) # reconnecting-websocket.min.js:1
I've also checked the documentation https://channels.readthedocs.io/en/stable/routing.html just to be sure this is how you set a path.
Your routing is correct, but you are sending the message too soon, while the socket is still trying to connect. Use the onopen callback to ensure the message is sent only after the connection was established:
socket.onopen = function() {
var message = {
action: "incomplete",
job_name: "10",
};
socket.send(JSON.stringify(message));
}

Send m-search packets on all network interfaces

I am implementing a code through which i have to get devices connected to all network interfaces on my machine.
For this, i am first getting the ip of all network interfaces and then sending m-search command on them.
After 2.5 seconds port is stopped to listen.
But it is giving me some assertion error.
Code:
class Base(DatagramProtocol):
""" Class to send M-SEARCH message to devices in network and receive datagram
packets from them
"""
SSDP_ADDR = "239.255.255.250"
SSDP_PORT = 1900
MS = "M-SEARCH * HTTP/1.1\r\nHOST: {}:{}\r\nMAN: 'ssdp:discover'\r\nMX: 2\r\nST: ssdp:all\r\n\r\n".format(SSDP_ADDR, SSDP_PORT)
def sendMsearch(self):
""" Sending M-SEARCH message
"""
ports = []
for address in self.addresses:
ports.append(reactor.listenUDP(0, self, interface=address))
for port in ports:
for num in range(4):
port.write(Base.MS, (Base.SSDP_ADDR,Base.SSDP_PORT))
reactor.callLater(2.5, self.stopMsearch, port) # MX + a wait margin
def stopMsearch(self, port):
""" Stop listening on port
"""
port.stopListening()
Error:
Traceback (most recent call last):
File "work\find_devices.py", line 56, in sendMsearch
ports.append(reactor.listenUDP(0, self, interface=address))
File "C:\Python27\lib\site-packages\twisted\internet\posixbase.py", line 374, in listenUDP
p.startListening()
File "C:\Python27\lib\site-packages\twisted\internet\udp.py", line 172, in startListening
self._connectToProtocol()
File "C:\Python27\lib\site-packages\twisted\internet\udp.py", line 210, in _connectToProtocol
self.protocol.makeConnection(self)
File "C:\Python27\lib\site-packages\twisted\internet\protocol.py", line 709, in makeConnection
assert self.transport == None
AssertionError
Please tell what's wrong in this code and how to correct this.
Also on linux machines, if no device is found on network then it doesn't go to stopMsearch() why ?
A protocol can only have one transport. The loop:
for address in self.addresses:
ports.append(reactor.listenUDP(0, self, interface=address))
tries to create multiple UDP transports and associate them all with self - a single protocol instance.
This is what the assertion error is telling you. The protocol's transport must be None (ie, it must not have a transport). But on the second iteration through the loop, it already has a transport.
Try using multiple protocol instances instead.