{"id":21647427,"url":"https://github.com/fitomad/websockets","last_synced_at":"2026-05-18T08:08:23.105Z","repository":{"id":74267687,"uuid":"107534695","full_name":"fitomad/websockets","owner":"fitomad","description":"WebSockets \u0026 Tornado","archived":false,"fork":false,"pushed_at":"2017-10-19T14:10:21.000Z","size":329,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-25T05:09:32.992Z","etag":null,"topics":["html5","javascript","python","python-2","tornado","tornado-websocket","websocket","websockets"],"latest_commit_sha":null,"homepage":"","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fitomad.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-10-19T10:58:24.000Z","updated_at":"2017-10-19T14:11:06.000Z","dependencies_parsed_at":"2023-07-12T04:45:36.566Z","dependency_job_id":null,"html_url":"https://github.com/fitomad/websockets","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitomad%2Fwebsockets","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitomad%2Fwebsockets/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitomad%2Fwebsockets/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitomad%2Fwebsockets/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fitomad","download_url":"https://codeload.github.com/fitomad/websockets/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244531013,"owners_count":20467391,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["html5","javascript","python","python-2","tornado","tornado-websocket","websocket","websockets"],"created_at":"2024-11-25T06:49:52.960Z","updated_at":"2026-05-18T08:08:18.082Z","avatar_url":"https://github.com/fitomad.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# WebSockets\n[WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) sobre [Tornado](http://www.tornadoweb.org/en/stable/index.html) Web Server.\n\n## Introducción\n\nUna prueba de concepto para probar los WebSockets presentes desde HTML5 usando el servidor Tornado de python. \n\nEl proyecto es un chat muy básico en el que a nivel de servidor podemos indicar que usuarios van a recibir determinados mensajes.\n\n## Requisitos\n\nPara poder ejecutar el proyecto necesitamos tener instalado... \n\n* Python 2.7\n* Tornado Web Server\n* Un navegador con soporte para WebSocket. Safari o Google Chrome, por ejemplo\n\n Para comprobar si tenemos instalado Tornado en nuestro equipo ejecutamos la siguiente instrucción en la línea de comando.\n```\n pip list --format=columns\n```\n Si está instalado veremos que aparece en el listado junto con su número de versión. En mi caso veo esta línea:\n```\n tornado    4.5.2\n```\n Si no la ves es que el servidor web no está presente, así que lo instalamos mediante el comando:\n```\n pip install tornado\n ```\n o si estamos en macOS es mejor usar...\n ```\n sudo pip install tornado\n ```\n\n## Ejecutando el proyecto\n\nClonamos/Descargamos este repositorio y abrimos un `Terminal`. Nos situamos dentro de la carpeta **src** y ejecutamos este comando:\n```\npython server.py\n```\n\nAhora el servicio está a la espera de nuevas conexiones, por lo que vamos a abrir **dos navegadores** y apuntaremos a la URL:\n```\nhttp://localhost:9090\n``` \nAparecerá la ventana de login de usuario. Es importante que elijamos *distintos* nombres de usuario pero *la misma* sala de chat para una primera prueba.  \n\nTras validarnos iremos al chat, y ahora escribiremos en mensajes en los dos navegadores para ver como cada uno de ellos recibe el mensaje enviado por el otro.\n\n## Probando el envío selectivo de mensajes\nPara esta prueba vamos a abrir **4 pestañas** del navegador y en la página de login vamos a poner **4 nombres de usuario diferentes** y en las salas de chat haremos que **2 de ellos** vayan a la sala de *swift-dev* y **los otros 2** a la sala *python-dev*.\n\nY ahora vamos a mandar mensajes con todos los usuarios, pero en este caso veremos que el servidor *sólo envía los mensajes a aquellos usuarios que comparten sala*, obviando al resto.\n\n## Vale. Ahora vamos a verle las tripas a esto.\nTenemos que diferenciar entre el código del cliente, escrito en JavaScript, y el servidor, escrito en Python.\n\n### El cliente\nLo que nos interesa se encuentra en el archivo `chat.js`. Aquí es donde vamos a ver todo el código necesario para conectar, enviar y recibir mensajes.\n\nLo primero que necesitamos es abrir una conexión con el servidor, y para ello usamos un objeto de la clase `WebSocket`\n```javascript\nvar ws = new WebSocket(\"ws://localhost:9090/talking\")\n```\nSi nos fijamos veremos que los WebSockets utilizan el protocolo `ws` en lugar de `http`. \n\nUna vez que hemos abierto una conexión toda la comunicación que recibamos del servidor la recogeremos en eventos que la clase `WebSocket` tiene a tal efecto.\n\nEn concreto recibiremos una evento al **conectar**\n```javascript\nws.onopen = function() {\n    console.log(\"Conectado...\")\n}\n```\n...y un evento cuando recibamos un mensaje\n```javascript\nws.onmessage = function (evt) {\n    console.log(evt.data)\n}\n```\nTambién podemos recoger [eventos](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) cuando se produzca un error o se cierre la conexión.\n\nVale, pero... ¿cómo envío un mensaje al servidor?\n```javascript\nws.send(\"lo que quieras decir\")\n```\nasí de fácil y así de sencillo.\n\n### El servidor\nHasta ahora hemos visto como se envía un mensaje, pero qué tenemos que hacer para que lo reciban el resto de participantes.\n\nLo que nos interesa saber lo encontramos en el archivo `chat.py`, en el raiz del repositorio. Este archivo contiene la clase encargada de `manejar` las conexión WebSocket y difundir los mensajes a los clientes. \n\nCada uno de estos clientes se representa mediante la clase `ChatClient` que encontramos en el archivo `client.py`\n\nLo primero que vemos en la clase `ChatWebSocketHandler`es que hereda de [`tornado.web.WebSocketHandler`](http://www.tornadoweb.org/en/stable/websocket.html), un tipo de manejador específico que Tornado pone a nuestra disposición para manejar las conexiones WebSocket.\n\nAl igual que en los manejadores para conexiones `HTTP`, la clase `WebSocketHandler` nos deja sobreescribir una serie de funciones en las que podemos controlar la *conexión, recepción de mensajes y cierre de comunicación* con **todos** y cada uno de los clientes que se conectan a nuestro servicio.\n\n```python\n# Se abre un canal de comunicación\ndef open(self):\n    ...\n# Recibimos un mensaje (data) de un cliente.\ndef on_message(self, data):\n    ...\n# Se ha cerrado un canal de comunicación\ndef on_close(self):\n    ...\n# Se ha recibido un PING\ndef on_ping(data)\n    ...\n```\n\n¿Y cómo enviamos un mensaje a los clientes?\n\nPara eso tenemos la función [`write_message(data)`](http://www.tornadoweb.org/en/stable/websocket.html#tornado.websocket.WebSocketHandler.write_message) donde data es lo que le enviamos al cliente. \n\n¿Y con esto respondo a **todos** los clientes?\n\nNo. Con esto **sólo** le enviamos el contenido de `data` al cliente que maneja esta WebHandlerSocket.\n\n¿Y qué hago para responder a **todos** o a **algunos**?\n\nPues vamos a tener que programar un poco. Pero tranquilos que es *poco* de verdad. \n\nLo primero que vamos a hacer es crearnos una clase que contenga la información relacionada con cada cliente, en nuestro caso es `ChatClient`\n\n```python\nclass ChatClient:\n    # WebSocket asociado\n    connection = None\n\n    #\n    # Nueva Conexion\n    #\n    def __init__(self, nick, room, createdAt):\n        self.nick = nick\n        self.room = room\n        self.createdAt = createdAt\n```\n\n¿Y esto se crea en la función `open()' del servidor? \n\nMe temo que no, vamos a tener que definir un formato de mensajes usando `json` para intercambiar mensajes con el servidor. \n\nEn este caso he definido **3 tipos de mensajes**\n\n* **login**: Un nuevo usuario se une al chat y a una sala en concreto\n* **room**: Es un mensaje de difusión a una sala\n* **private**: Un mensaje privado a un usuario.\n\nEn el servidor tenemos que diferenciar entre los mensajes de `login` y lo que son `room` o `private`. El primero no se envía a los demás usuarios, y los otros dos sólo a aquellos usuarios a los que les pueda interesar\n\nAsí que cuando un usuario hace login en la web se envía un mensaje al servidor de tipo `login` donde le dedimos el *nickname* y la *sala* a la que se conecta.\n\nCuando el servidor recibe el mensaje guarda esa conexión (clase ChatClient) en variable llamada `clients` donde están almacenadas **todas las conexiones** abiertas con el servicio.\n\nSi el usuario manda un mensaje el servidor, tras comprobar que es de tipo `room` o `private`, filtra el array de clientes para recuperar sólo aquellos que están en la misma sala (si es `room`) o el cliente cuyo nick coincide con el especificado en el mensaje (si es `private`)\n\n```python\ndef on_message(self, data):\n    msg = json.loads(data)\n\n    if msg[\"type\"] == \"login\":\n        self.manage_login(msg)\n    else:\n        room = msg[\"to\"]\n        self.manage_message(room, data)\n\ndef manage_login(self, message):\n    client = ChatClient(message[\"nickname\"], message[\"room\"], message[\"connectedAt\"])\n    client.connection = self\n\n    ChatWebSocketHandler.clients.append(client)\n\ndef manage_message(self, room, message):\n    # Filtramos los clientes que esten en la misma sala\n    filter_func = lambda e : e.room == room\n    room_clients = filter(filter_func, ChatWebSocketHandler.clients)\n    \n    for client in room_clients:\n        client.connection.write_message(message)        \n```\n\nUna vez el servidor tiene los clientes interesados en recibir el mensajes usa la función `write_message(message)` de **cada uno de los clientes/conexiones**\n\n## Contacto\nCualquier duda o pregunta podéis encontrarme en twitter con el nombre de usuario [@fitomad](https://twitter.com/fitomad)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffitomad%2Fwebsockets","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffitomad%2Fwebsockets","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffitomad%2Fwebsockets/lists"}