Comments (5)
part of the puzzle solved... The issue lies here:
https://github.com/robertmrk/aiocometd/blob/develop/aiocometd/utils.py#L110
The id is optional but the check is wrong. It's actually sending an id in the payload but it does not retrieve that back.
Now the next issue is that it won't connect at all...
2019-04-10 23:51:13,417 INFO main -- outgoing payload: [{'channel': <MetaChannel.HANDSHAKE: '/meta/handshake'>, 'version': '1.0', 'supportedConnectionTypes': ['websocket', 'long-polling'], 'minimumVersion': '1.0'}]
2019-04-10 23:51:13,417 INFO main -- outgoing headers: {}
2019-04-10 23:51:13,431 INFO main -- incoming payload: [{'supportedConnectionTypes': ['long-polling', 'streaming'], 'channel': '/meta/handshake', 'advice': {'reconnect': 'retry', 'interval': 0, 'timeout': 60000}, 'successful': True, 'clientId': 'cee2cd2b', 'version': '1.0'}]
2019-04-10 23:51:13,431 INFO main -- incoming headers: <CIMultiDictProxy('Server': 'Logitech Media Server (7.9.2 - 1554701435)', 'Cache-Control': 'no-cache', 'Pragma': 'no-cache', 'Vary': 'Accept-Encoding', 'Content-Encoding': 'gzip', 'Content-Length': '174', 'Content-Type': 'application/json', 'Expires': '-1', 'X-Time-To-Serve': '0.00280499458312988')>
2019-04-10 23:51:13,432 INFO main -- outgoing payload: [{'channel': <MetaChannel.CONNECT: '/meta/connect'>, 'clientId': 'cee2cd2b', 'connectionType': 'long-polling'}]
2019-04-10 23:51:13,432 INFO main -- outgoing headers: {}
2019-04-10 23:52:13,458 INFO main -- incoming payload: [{'timestamp': 'Wed, 10 Apr 2019 21:51:15 GMT', 'advice': {'interval': 0}, 'channel': '/meta/connect', 'successful': True, 'clientId': 'cee2cd2b'}]
2019-04-10 23:52:13,458 INFO main -- incoming headers: <CIMultiDictProxy('Server': 'Logitech Media Server (7.9.2 - 1554701435)', 'Cache-Control': 'no-cache', 'Pragma': 'no-cache', 'Content-Length': '137', 'Content-Type': 'application/json', 'Expires': '-1', 'X-Time-To-Serve': '60.0037240982056')>
2019-04-10 23:52:13,458 INFO main -- outgoing payload: [{'channel': <MetaChannel.SUBSCRIBE: '/meta/subscribe'>, 'clientId': 'cee2cd2b', 'subscription': '/**'}]
2019-04-10 23:52:13,459 INFO main -- outgoing headers: {}
2019-04-10 23:52:13,460 INFO main -- outgoing payload: [{'channel': <MetaChannel.CONNECT: '/meta/connect'>, 'clientId': 'cee2cd2b', 'connectionType': 'long-polling'}]
2019-04-10 23:52:13,460 INFO main -- outgoing headers: {}
2019-04-10 23:53:13,561 INFO main -- incoming payload: [{'advice': {'interval': 0}, 'channel': '/meta/connect', 'successful': True, 'timestamp': 'Wed, 10 Apr 2019 21:52:15 GMT', 'clientId': 'cee2cd2b'}]
2019-04-10 23:53:13,561 INFO main -- incoming headers: <CIMultiDictProxy('Server': 'Logitech Media Server (7.9.2 - 1554701435)', 'Cache-Control': 'no-cache', 'Pragma': 'no-cache', 'Content-Length': '137', 'Content-Type': 'application/json', 'Expires': '-1', 'X-Time-To-Serve': '60.003270149231')>
2019-04-10 23:53:13,562 INFO main -- outgoing payload: [{'channel': <MetaChannel.CONNECT: '/meta/connect'>, 'clientId': 'cee2cd2b', 'connectionType': 'long-polling'}]
2019-04-10 23:53:13,562 INFO main -- outgoing headers: {}
2019-04-10 23:53:27,590 INFO main -- outgoing payload: [{'channel': <MetaChannel.DISCONNECT: '/meta/disconnect'>, 'clientId': 'cee2cd2b'}]
2019-04-10 23:53:27,591 INFO main -- outgoing headers: {}
2019-04-10 23:53:27,602 INFO main -- incoming payload: [{'clientId': 'cee2cd2b', 'channel': '/meta/disconnect', 'successful': True, 'timestamp': 'Wed, 10 Apr 2019 21:53:30 GMT'}]
2019-04-10 23:53:27,602 INFO main -- incoming headers: <CIMultiDictProxy('Server': 'Logitech Media Server (7.9.2 - 1554701435)', 'Cache-Control': 'no-cache', 'Connection': 'close', 'Pragma': 'no-cache', 'Content-Length': '116', 'Content-Type': 'application/json', 'Expires': '-1', 'X-Time-To-Serve': '0.00229215621948242')>
Task exception was never retrieved
future: <Task finished coro=<Main.lms_events() done, defined at main.py:60> exception=TransportError('None')>
Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/aiocometd/transports/long_polling.py", line 34, in _send_final_payload
timeout=self.request_timeout)
File "/usr/local/lib/python3.7/site-packages/aiohttp/client.py", line 497, in _request
await resp.start(conn)
File "/usr/local/lib/python3.7/site-packages/aiohttp/client_reqrep.py", line 844, in start
message, payload = await self._protocol.read() # type: ignore # noqa
File "/usr/local/lib/python3.7/site-packages/aiohttp/streams.py", line 588, in read
await self._waiter
aiohttp.client_exceptions.ServerDisconnectedError: None
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "main.py", line 68, in lms_events
await client.subscribe("/**")
File "/usr/local/lib/python3.7/site-packages/aiocometd/client.py", line 315, in subscribe
response = await self._transport.subscribe(channel)
File "/usr/local/lib/python3.7/site-packages/aiocometd/transports/base.py", line 633, in subscribe
subscription=channel)
File "/usr/local/lib/python3.7/site-packages/aiocometd/transports/base.py", line 303, in _send_message
return await self._send_payload_with_auth([message])
File "/usr/local/lib/python3.7/site-packages/aiocometd/transports/base.py", line 318, in _send_payload_with_auth
response = await self._send_payload(payload)
File "/usr/local/lib/python3.7/site-packages/aiocometd/transports/base.py", line 345, in _send_payload
return await self._send_final_payload(payload, headers=headers)
File "/usr/local/lib/python3.7/site-packages/aiocometd/transports/long_polling.py", line 39, in _send_final_payload
raise TransportError(str(error)) from error
aiocometd.exceptions.TransportError: None
from aiocometd.
btw I'm using this as reference: https://github.com/CDrummond/lms-material/blob/master/MaterialSkin/HTML/material/html/js/server.js#L120
from aiocometd.
Hi @marcelveldt
The problem is that the server implementation is not returning the message id
fields in its responses. Even though the id
field is generally speaking optional in messages, but if the client decides to send them, the server must include them in its response messages.
Generation of IDs is implementation specific and may be provided by the application. Messages published to /meta/** and /service/** SHOULD have id fields that are unique within the connection.
Messages sent in response to messages delivered to /meta/** channels MUST use the same message id as the request message.
Messages sent in response to messages delivered to /service/** channels SHOULD use the same message id as the request message or an id derived from the request message id.
The lack of id
fields may not be an issue for the official Java and JavaScript clients, but it is for aiocometd. I have to explain some design decisions first...
The official clients have an asynchronous API. When you're executing a handshake, subscribe/unsubscribe to channels or send service or broadcast messages with them, the methods immediately return, and if one of these operations fail then the only way to detect this failure is to register a message listener callback for these channels.
I could've chose this method for handling responses, but I was really unsatisfied with this approach. To make the usage of the library as easy and straightforward as possible, I made the decisions that if an error occurs then I want to raise an error at the exactly same point where the error is made. So for example if a subscription attempt fails because of a network issues or if the server denies the subscription then I want to raise an error from the subscribe
method of the client, and not in a callback function somewhere.
In order the achieve this, the library has to be able to associate request messages with responses, and without an id
field this would be impossible.
If the library would support only the long-polling
transport then it might be doable even without the id
, but not for the websocket
transport.
For example, in the case of the websocket
transport, let's say that the client sends two messages to the same channel in rapid succession.
await client.publish("/channel_name", message1)
await client.publish("/channel_name", message2)
On the transport's level, there are no response-request pairs, only a two way stream of messages. After the above outgoing messages the transport would get a response payload, and it would be able to tell that the payload contains a response for a message published on /channel_name
. But it wouldn't be able to tell whether the response came for message1
or message2
.
The only way to solve this issue on my side is by completely changing the API of aiocometd and the API of libraries that depend on it, such as aiosfstream.
Since the way I interpret the specs, the id
fields are mandatory in responses if the client sends them in the requests, and since the official CometD server implementations handle the id
fields correctly I think it would make more sense to fix this problem on the side of the Logitech Media Server.
from aiocometd.
Thanks for your very detailed response!
I've just managed to fix it. It turns out there were 2 different issues:
- The LMS server only doesn't need the ID for the connect and handshake message, it does use the id for other messages.
- Turns out LMS needs some custom channel names and needs some additional data to be passed on the subscription message.
I was able to solve all issues by just using the Extension class:
class LMSExtension(Extension):
''' Extension for the custom cometd implementation of LMS'''
async def incoming(self, payload, headers=None):
pass
async def outgoing(self, payload, headers):
''' override outgoing messages to fit LMS custom implementation'''
# handle subscriptions
if 'subscribe' in payload[0]['channel']:
client_id = payload[0]['clientId']
if payload[0]['subscription'] == '/slim/subscribe/serverstatus':
# append additional request data to the request
payload[0]['data'] = {'response':'/%s/slim/serverstatus' % client_id,
'request':['', ['serverstatus', 0, 100, 'subscribe:0']]}
payload[0]['channel'] = '/slim/subscribe'
if payload[0]['subscription'].startswith('/slim/subscribe/playerstatus'):
# append additional request data to the request
player_id = payload[0]['subscription'].split('/')[-1]
payload[0]['data'] = {'response':'/%s/slim/playerstatus/%s' % (client_id, player_id),
'request':[player_id, ["status", "-", 1, "tags:cdeloyrstAKNS", "subscribe:0"]]}
payload[0]['channel'] = '/slim/subscribe'
# LMS does not need/want id for the connect and handshake message
if payload[0]['channel'] in ['/meta/handshake','/meta/connect']:
del payload[0]['id']
So far this seems to work perfectly fine. Thanks!
from aiocometd.
Hi @marcelveldt
This is a quite clever solution to this problem. The only issue is that it relies on the internals of the library. If the request-response matching technique changes, this solution might not work anymore. (I have no plans to change it at the moment)
I'll keep this issue in mind and I'll try to find a way to refactor the library in a future release, to rely less on the presence of the id
field.
Thank you very much for reporting this issue!
from aiocometd.
Related Issues (15)
- Client is not reconnecting after handshake. HOT 9
- Error: Attempt to decode JSON
- TypeError: outgoing() takes 2 positional argument but 3 were given HOT 1
- Strange typing issue with JsonObject HOT 1
- Support for Python 3.10 HOT 4
- Is there a hook for reconnection?
- AuthExtension Example Request
- TypeError: Semaphore.__init__() got an unexpected keyword argument 'loop' in transport/long_polling.py file HOT 3
- Service channels & websockets only HOT 13
- using aiocometd to connect the CometD cluster of nginx reverse proxy error HOT 11
- Add check on status code to long-polling HOT 7
- Long Polling flow
- BUG: does not handle own CancelledError on client.close() HOT 3
- TypeError: string indices must be integers
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from aiocometd.