Git Product home page Git Product logo

Comments (5)

marcelveldt avatar marcelveldt commented on May 29, 2024

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.

marcelveldt avatar marcelveldt commented on May 29, 2024

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.

robertmrk avatar robertmrk commented on May 29, 2024

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.

marcelveldt avatar marcelveldt commented on May 29, 2024

Thanks for your very detailed response!

I've just managed to fix it. It turns out there were 2 different issues:

  1. The LMS server only doesn't need the ID for the connect and handshake message, it does use the id for other messages.
  2. 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.

robertmrk avatar robertmrk commented on May 29, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.