Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error in client after server has rejected connection #504

Closed
MaiKitty opened this issue Jun 8, 2020 · 10 comments
Closed

Error in client after server has rejected connection #504

MaiKitty opened this issue Jun 8, 2020 · 10 comments
Assignees

Comments

@MaiKitty
Copy link

MaiKitty commented Jun 8, 2020

I am trying to accomplish the following:
In case of invalid authentication by the client the socketio server should emit an event and disconnect the client.

However I get the following error on the client side:
Traceback (most recent call last): File "client.py", line 35, in <module> sio.connect("http://localhost:8080") File "C:\Users\Mai\.virtualenvs\python_socketio_auth-571J5SCS\lib\site-packages\socketio\client.py", line 282, in connect six.raise_from(exceptions.ConnectionError(exc.args[0]), None) File "<string>", line 3, in raise_from socketio.exceptions.ConnectionError: Unexpected status code 401 in server response

After having found this particular post I am aware that
there are issues with the server rejecting the connection.
However, I have already updated python-socketio to the newest version where you
improved the handling of rejected connections
.
The error above occurs using the newest version.

Below I provide the log-output of the server and code to reproduce this error

  1. log-output of the server
[2020-06-08 18:42:57,911] [engineio.server] [__init__] [INFO] Server initialized for sanic.
[2020-06-08 18:42:57 +0200] [19560] [INFO] Goin' Fast @ http://127.0.0.1:8080
[2020-06-08 18:42:57,914] [sanic.root] [_helper] [INFO] Goin' Fast @ http://127.0.0.1:8080
[2020-06-08 18:42:57 +0200] [19560] [INFO] Starting worker [19560]
[2020-06-08 18:42:57,916] [sanic.root] [serve] [INFO] Starting worker [19560]
[2020-06-08 18:43:17,479] [engineio.server] [send] [INFO] 539746d1b0404fa6ad2f0037d955fcbe: Sending packet OPEN data {'sid': '539746d1b0404fa6ad2f0037d955fcbe', 'upgrades': ['websocket'], 'pingTimeout': 60000, 'pingInterval': 25000}
[2020-06-08 18:43:17,480] [engineio.server] [send] [INFO] 539746d1b0404fa6ad2f0037d955fcbe: Sending packet MESSAGE data 0
[2020-06-08 18:43:17,484] [socketio.asyncserver] [emit] [INFO] emitting event "result" to 539746d1b0404fa6ad2f0037d955fcbe [/]
[2020-06-08 18:43:17,487] [engineio.server] [send] [INFO] 539746d1b0404fa6ad2f0037d955fcbe: Sending packet MESSAGE data 2["result","auth denied"]
[2020-06-08 18:43:17,488] [server] [connect] [WARNING] 539746d1b0404fa6ad2f0037d955fcbe: invalid auth. Server rejected the connection
[2020-06-08 18:43:17,489] [engineio.server] [send] [INFO] 539746d1b0404fa6ad2f0037d955fcbe: Sending packet MESSAGE data 1
[2020-06-08 18:43:17,490] [engineio.server] [_handle_connect] [WARNING] Application rejected connection
[2020-06-08 18:43:17 +0200] - (sanic.access)[INFO][127.0.0.1:58206]: GET http://localhost:8080/socket.io/?transport=polling&EIO=3&t=1591634595.4383996  401 14
  1. code for reproduction
  • Pipfile
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]

[packages]
sanic = "*"
sanic-cors = "*"
python-socketio = "*"
python-socketio-client = "*"

[requires]
python_version = "3.7"
  • server.py
import logging
import argparse

from sanic import Sanic
from sanic_cors import CORS
import socketio

# Configure root logger.
logging.basicConfig(
    level=logging.INFO,
    format='[%(asctime)s] [%(name)s] [%(funcName)s] [%(levelname)s] %(message)s'
)

# Initialize logger
logger = logging.getLogger('server')
socketio_logger = logging.getLogger('socketio.asyncserver')


# Create an asyncio compatible Socket.IO server
# Parameter description: https://python-socketio.readthedocs.io/en/latest/api.html#asyncserver-class
# "always_connect" is set to true to allow the server to emit events in the connect event handler
sio = socketio.AsyncServer(
    logger=socketio_logger,
    async_handlers=True,
    async_mode='sanic',
    allow_upgrades=True,
    monitor_clients=True,
    cors_allowed_origins=[],
    always_connect=True
)


# Attach Socket.IO server to a Sanic application
app = Sanic(name="python-socketio-auth-test")
app.config['CORS_SUPPORTS_CREDENTIALS'] = True
CORS(app)
sio.attach(app)


@sio.on('connect')
async def connect(sid, environ):
    await sio.emit('result', "auth denied", room=sid)
    logger.warning(f'{sid}: invalid auth. Server rejected the connection', extra={'client_sid':sid})
    return False


# Instantiate the argument parser
parser = argparse.ArgumentParser()

# Add arguments to parser
parser.add_argument('--host', type=str, nargs=1, default=['127.0.0.1'])
parser.add_argument('--port', type=str, nargs=1, default=['8080'])

if __name__ == '__main__':
    try:
        # Parse commandline arguments
        args = parser.parse_args()
        host = args.host[0]
        port = args.port[0]

        app.run(host=host, port=port, debug=False, access_log=True)
    except:
        logger.exception(f"An excpetion occurred.", exc_info=True)
  • client.py
import logging
import threading

import socketio

# Initialize and configure logging
logging.basicConfig(
    format='[%(asctime)s] [%(name)s] [%(funcName)s] [%(levelname)s] %(message)s')
logger = logging.getLogger('test_python_socketio_auth')
logger.setLevel(logging.INFO)


sio = socketio.Client(logger=logger)
message = None

# Initialize event which is used to signal that a server response has been received
server_response_received = threading.Event()


@sio.on('result')
def on_match_result(json):
    global message
    message = json
    server_response_received.set()


@sio.event
def connect_error(msg):
    global message
    message = msg
    server_response_received.set()


# Try to establish a connection
sio.connect("http://localhost:8080")

# Wait (max. 2s) for the server response to be received
server_response_received.wait(timeout=2)

logger.info(f'Server responded to the connection attempt with the following message: {message}')
logger.info(f'Client connected: {sio.connected}')

if sio.connected:
    # Disconnect from the server
    sio.disconnect()

Am I doing something wrong or is this an issue with python-socketio?

@miguelgrinberg
Copy link
Owner

What did you expect should happen? The error that you are getting is a ConnectionError exception, that's the expected outcome when the connection is rejected by the server.

@MaiKitty
Copy link
Author

MaiKitty commented Jun 9, 2020

I expected the following:

  • The client should be able to process the message emitted from the server in the event handler on_match_result.
  • The connection being refused can be handled in the event handler connect_error without an Exception being thrown when calling sio.connect

After adapting the handlers in the client the following way:

@sio.on('result')
def on_match_result(json):
    global message
    message = json
    logger.info(f'Server responded to the connection attempt with the following message to "result": {message}')
    server_response_received.set()


@sio.event
def connect_error(msg):
    global message
    message = msg
    logger.info(f'Server responded to the connection attempt with the connect_error: {message}')
    server_response_received.set()

I get the following output on the serverside:

[2020-06-09 12:04:03,549] [test_python_socketio_auth] [connect_error] [INFO] Server responded to the connection attempt with the connect_error: Unauthorized
Traceback (most recent call last):
  File ".\src\client.py", line 37, in <module>
    sio.connect("http://localhost:8080")
  File "C:\Users\Mai\.virtualenvs\python_socketio_auth-571J5SCS\lib\site-packages\socketio\client.py", line 282, in connect
    six.raise_from(exceptions.ConnectionError(exc.args[0]), None)
  File "<string>", line 3, in raise_from
socketio.exceptions.ConnectionError: Unexpected status code 401 in server response

This way the client does not process the result-event.

@miguelgrinberg
Copy link
Owner

@MaiKitty you can't emit and then reject the connection, that's not how things work. A rejected connection is never established, there is no transport set up on which an event can travel. Take a look at the always_connect option which changes the way connections are accepted and rejected to something closer to what you expect.

@MaiKitty
Copy link
Author

MaiKitty commented Jun 11, 2020

I have already set always_connect to True in the example code shown above. That's why I expected that the server is allowed to emit events before returning False in the connect-handler.

@miguelgrinberg
Copy link
Owner

miguelgrinberg commented Jun 11, 2020

Hmm. Okay, sorry, I missed that. I need to check if something broke with regards to the always_connect option then.

@miguelgrinberg miguelgrinberg self-assigned this Jun 11, 2020
@MaiKitty
Copy link
Author

Thank you :)

@miguelgrinberg
Copy link
Owner

Quick update. It appears that the always_connect has never worked properly in that there was no actual disconnect when the connect event rejected the client by returning False.

Then, I recently fixed another problem with rejected connections when always_connect is not used, and this fix made the rejection for always_connect not work at all.

So the plan is for me to a) make sure a rejected connection does not return a 401 error when always_connect == True, and b) figure out how to properly disconnect these clients, something that I have never implemented before.

@MaiKitty
Copy link
Author

MaiKitty commented Jun 17, 2020

Thanks for the update! The plan sounds good to me. Can you recommend any alternatives in the mean time?

@miguelgrinberg
Copy link
Owner

The alternative is to use the normal method for rejecting connections, which is to not emit anything if the connection is going to be rejected.

I really never intended to support what you are trying to do, really. After looking at this and how complicated it would be to implement I'm actually thinking that I may end up documenting this as a limitation. So basically you will be able to call emit() in the connect handler, but that automatically accepts the connection, so you cannot then reject it.

@MaiKitty
Copy link
Author

Alright, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants