Usar un handler HTTP que gestiona WebSockets
Con un handler HTTP que también gestione WebSockets podemos gestionar los WebSockets en el mismo puerto HTTP, así no es necesario usar / abrir dos puertos. Para eso he usado el handler creado por SevenW en: https://github.com/SevenW/httpwebsockethandler y que extiende el SimpleHTTPServer. Aunque ha sido ligeralmente modificado: - He creado un wrapper para las peticiones HTTP GET, el antiguo do_GET ahora es do_GET_HTTP - He desactivado una parte de autenticación que tenía (ni he mirado en intentar mantelerlo, aunque si mantenemos el del do_GET_HTTP, pero supongo que también debería protegerse el de las querys WS y seguramente sería muy muy sencillo reactivarle esa funcionaliad y quitarle el comentario) En el server mantenemos el antiguo do_GET pero renombrado a do_GET_HTTP, y se han movido los métodos del WebSocketServer a este handler. He quitado las cosas que configuran un puerto WebSocket ya que ahora es el mismo que HTTP. Había pensado en enviarlo en otra carpetita (mediaserver-alt) o algo así, por si acaso usar lo otro tiene algún setido (como que no se usen ciertas clases por compatibilidad con pythons antiguos), pero bueno, por ahora lo dejo aquí, siempre se puede hacer rollback + crear una nueva carpeta.
This commit is contained in:
150
mediaserver/HTTPAndWSServer.py
Normal file
150
mediaserver/HTTPAndWSServer.py
Normal file
@@ -0,0 +1,150 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import random
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
from BaseHTTPServer import HTTPServer
|
||||
from HTTPWebSocketsHandler import HTTPWebSocketsHandler
|
||||
|
||||
from platformcode import config, logger
|
||||
from core import jsontools as json
|
||||
|
||||
class MyHTTPServer(HTTPServer):
|
||||
daemon_threads = True
|
||||
|
||||
def process_request_thread(self, request, client_address):
|
||||
try:
|
||||
self.finish_request(request, client_address)
|
||||
self.shutdown_request(request)
|
||||
except:
|
||||
self.handle_error(request, client_address)
|
||||
self.shutdown_request(request)
|
||||
|
||||
def process_request(self, request, client_address):
|
||||
ID = "%032x" % (random.getrandbits(128))
|
||||
t = threading.Thread(target=self.process_request_thread,
|
||||
args=(request, client_address), name=ID)
|
||||
t.daemon = self.daemon_threads
|
||||
t.start()
|
||||
|
||||
def handle_error(self, request, client_address):
|
||||
import traceback
|
||||
if not "socket.py" in traceback.format_exc():
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
class Handler(HTTPWebSocketsHandler):
|
||||
def log_message(self, format, *args):
|
||||
# sys.stderr.write("%s - - [%s] %s\n" %(self.client_address[0], self.log_date_time_string(), format%args))
|
||||
pass
|
||||
|
||||
def sendMessage(self, message):
|
||||
self.send_message(message)
|
||||
|
||||
def do_GET_HTTP(self):
|
||||
from platformcode import platformtools
|
||||
from platformcode import controllers
|
||||
# Control de accesos
|
||||
Usuario = "user"
|
||||
Password = "password"
|
||||
ControlAcceso = False
|
||||
import base64
|
||||
# Comprueba la clave
|
||||
if ControlAcceso and self.headers.getheader('Authorization') <> "Basic " + base64.b64encode(
|
||||
Usuario + ":" + Password):
|
||||
self.send_response(401)
|
||||
self.send_header('WWW-Authenticate',
|
||||
'Basic realm=\"Introduce el nombre de usuario y clave para acceder a alfa\"')
|
||||
self.send_header('Content-type', 'text/html; charset=utf-8')
|
||||
self.end_headers()
|
||||
self.wfile.write('¡Los datos introducidos no son correctos!')
|
||||
return
|
||||
|
||||
data = re.compile('/data/([^/]+)/([^/]+)/([^/]+)', re.DOTALL).findall(self.path)
|
||||
if data:
|
||||
data = data[0]
|
||||
if data[0] in platformtools.requests:
|
||||
c = platformtools.requests[data[0]]
|
||||
response = {"id": data[1], "result": data[2]}
|
||||
print response
|
||||
c.handler = self
|
||||
c.set_data(response)
|
||||
while data[0] in platformtools.requests and not self.wfile.closed:
|
||||
time.sleep(1)
|
||||
else:
|
||||
if self.path == "": self.path = "/"
|
||||
|
||||
# Busca el controller para la url
|
||||
controller = controllers.find_controller(self.path)
|
||||
if controller:
|
||||
try:
|
||||
c = controller(self)
|
||||
c.run(self.path)
|
||||
except:
|
||||
if not "socket.py" in traceback.format_exc():
|
||||
logger.error(traceback.format_exc())
|
||||
finally:
|
||||
c.__del__()
|
||||
del c
|
||||
return
|
||||
|
||||
def on_ws_message(self, message):
|
||||
try:
|
||||
if message:
|
||||
json_message = json.load(message)
|
||||
|
||||
if "request" in json_message:
|
||||
t = threading.Thread(target=run, args=[self.controller, json_message["request"].encode("utf8")], name=self.ID)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
|
||||
elif "data" in json_message:
|
||||
if type(json_message["data"]["result"]) == unicode:
|
||||
json_message["data"]["result"] = json_message["data"]["result"].encode("utf8")
|
||||
|
||||
self.controller.data = json_message["data"]
|
||||
|
||||
except:
|
||||
logger.error(traceback.format_exc())
|
||||
show_error_message(traceback.format_exc())
|
||||
|
||||
def on_ws_connected(self):
|
||||
try:
|
||||
self.ID = "%032x" % (random.getrandbits(128))
|
||||
from platformcode.controllers.html import html
|
||||
self.controller = html(self, self.ID)
|
||||
self.server.fnc_info()
|
||||
except:
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
def on_ws_closed(self):
|
||||
self.controller.__del__()
|
||||
del self.controller
|
||||
self.server.fnc_info()
|
||||
|
||||
def address_string(self):
|
||||
# Disable reverse name lookups
|
||||
return self.client_address[:2][0]
|
||||
|
||||
|
||||
PORT = config.get_setting("server.port")
|
||||
server = MyHTTPServer(('', int(PORT)), Handler)
|
||||
|
||||
def run(controller, path):
|
||||
try:
|
||||
controller.run(path)
|
||||
except:
|
||||
logger.error(traceback.format_exc())
|
||||
show_error_message(traceback.format_exc())
|
||||
|
||||
|
||||
def start(fnc_info):
|
||||
server.fnc_info = fnc_info
|
||||
threading.Thread(target=server.serve_forever).start()
|
||||
|
||||
|
||||
def stop():
|
||||
server.socket.close()
|
||||
server.shutdown()
|
||||
@@ -1,89 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ------------------------------------------------------------
|
||||
# HTTPServer
|
||||
# ------------------------------------------------------------
|
||||
import os
|
||||
import random
|
||||
import traceback
|
||||
from threading import Thread
|
||||
|
||||
import WebSocketServer
|
||||
|
||||
from core import jsontools as json
|
||||
from platformcode import config, platformtools, logger
|
||||
|
||||
|
||||
class HandleWebSocket(WebSocketServer.WebSocket):
|
||||
def handleMessage(self):
|
||||
try:
|
||||
if self.data:
|
||||
json_message = json.load(str(self.data))
|
||||
|
||||
if "request" in json_message:
|
||||
t = Thread(target=run, args=[self.controller, json_message["request"].encode("utf8")], name=self.ID)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
|
||||
elif "data" in json_message:
|
||||
if type(json_message["data"]["result"]) == unicode:
|
||||
json_message["data"]["result"] = json_message["data"]["result"].encode("utf8")
|
||||
|
||||
self.controller.data = json_message["data"]
|
||||
|
||||
except:
|
||||
logger.error(traceback.format_exc())
|
||||
show_error_message(traceback.format_exc())
|
||||
|
||||
def handleConnected(self):
|
||||
try:
|
||||
self.ID = "%032x" % (random.getrandbits(128))
|
||||
from platformcode.controllers.html import html
|
||||
self.controller = html(self, self.ID)
|
||||
self.server.fnc_info()
|
||||
except:
|
||||
logger.error(traceback.format_exc())
|
||||
self.close()
|
||||
|
||||
def handleClose(self):
|
||||
self.controller.__del__()
|
||||
del self.controller
|
||||
self.server.fnc_info()
|
||||
|
||||
|
||||
port = config.get_setting("websocket.port")
|
||||
server = WebSocketServer.SimpleWebSocketServer("", int(port), HandleWebSocket)
|
||||
|
||||
|
||||
def start(fnc_info):
|
||||
server.fnc_info = fnc_info
|
||||
Thread(target=server.serveforever).start()
|
||||
|
||||
|
||||
def stop():
|
||||
server.close()
|
||||
|
||||
|
||||
def run(controller, path):
|
||||
try:
|
||||
controller.run(path)
|
||||
except:
|
||||
logger.error(traceback.format_exc())
|
||||
show_error_message(traceback.format_exc())
|
||||
|
||||
|
||||
def show_error_message(err_info):
|
||||
from core import scrapertools
|
||||
patron = 'File "' + os.path.join(config.get_runtime_path(), "channels", "").replace("\\", "\\\\") + '([^.]+)\.py"'
|
||||
canal = scrapertools.find_single_match(err_info, patron)
|
||||
if canal:
|
||||
platformtools.dialog_ok(
|
||||
"Se ha producido un error en el canal " + canal,
|
||||
"Esto puede ser devido a varias razones: \n \
|
||||
- El servidor no está disponible, o no esta respondiendo.\n \
|
||||
- Cambios en el diseño de la web.\n \
|
||||
- Etc...\n \
|
||||
Comprueba el log para ver mas detalles del error.")
|
||||
else:
|
||||
platformtools.dialog_ok(
|
||||
"Se ha producido un error en Alfa",
|
||||
"Comprueba el log para ver mas detalles del error.")
|
||||
@@ -14,8 +14,7 @@ from platformcode import config
|
||||
|
||||
sys.path.append(os.path.join(config.get_runtime_path(), 'lib'))
|
||||
from platformcode import platformtools, logger
|
||||
import HTTPServer
|
||||
import WebSocket
|
||||
import HTTPAndWSServer
|
||||
|
||||
http_port = config.get_setting("server.port")
|
||||
websocket_port = config.get_setting("websocket.port")
|
||||
@@ -67,8 +66,7 @@ def start():
|
||||
logger.info("server init...")
|
||||
config.verify_directories_created()
|
||||
try:
|
||||
HTTPServer.start(show_info)
|
||||
WebSocket.start(show_info)
|
||||
HTTPAndWSServer.start(show_info)
|
||||
|
||||
# Da por levantado el servicio
|
||||
logger.info("--------------------------------------------------------------------")
|
||||
@@ -91,9 +89,7 @@ def start():
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print 'Deteniendo el servidor HTTP...'
|
||||
HTTPServer.stop()
|
||||
print 'Deteniendo el servidor WebSocket...'
|
||||
WebSocket.stop()
|
||||
HTTPAndWSServer.stop()
|
||||
print 'Alfa Detenido'
|
||||
flag = False
|
||||
|
||||
|
||||
229
mediaserver/lib/HTTPWebSocketsHandler.py
Normal file
229
mediaserver/lib/HTTPWebSocketsHandler.py
Normal file
@@ -0,0 +1,229 @@
|
||||
'''
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (C) 2014, 2015 Seven Watt <info@sevenwatt.com>
|
||||
<http://www.sevenwatt.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
'''
|
||||
|
||||
# HTTPWebSocketHandler from SevenW: https://github.com/SevenW/httpwebsockethandler
|
||||
|
||||
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
import struct
|
||||
from base64 import b64encode
|
||||
from hashlib import sha1
|
||||
from mimetools import Message
|
||||
from StringIO import StringIO
|
||||
import errno, socket #for socket exceptions
|
||||
import threading
|
||||
|
||||
class WebSocketError(Exception):
|
||||
pass
|
||||
|
||||
class HTTPWebSocketsHandler(SimpleHTTPRequestHandler):
|
||||
_ws_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
|
||||
_opcode_continu = 0x0
|
||||
_opcode_text = 0x1
|
||||
_opcode_binary = 0x2
|
||||
_opcode_close = 0x8
|
||||
_opcode_ping = 0x9
|
||||
_opcode_pong = 0xa
|
||||
|
||||
mutex = threading.Lock()
|
||||
|
||||
def on_ws_message(self, message):
|
||||
"""Override this handler to process incoming websocket messages."""
|
||||
pass
|
||||
|
||||
def on_ws_connected(self):
|
||||
"""Override this handler."""
|
||||
pass
|
||||
|
||||
def on_ws_closed(self):
|
||||
"""Override this handler."""
|
||||
pass
|
||||
|
||||
def do_GET_HTTP(self):
|
||||
"""Override this handler."""
|
||||
SimpleHTTPRequestHandler.do_GET(self)
|
||||
pass
|
||||
|
||||
def send_message(self, message):
|
||||
self._send_message(self._opcode_text, message)
|
||||
|
||||
def setup(self):
|
||||
SimpleHTTPRequestHandler.setup(self)
|
||||
self.connected = False
|
||||
|
||||
# def finish(self):
|
||||
# #needed when wfile is used, or when self.close_connection is not used
|
||||
# #
|
||||
# #catch errors in SimpleHTTPRequestHandler.finish() after socket disappeared
|
||||
# #due to loss of network connection
|
||||
# try:
|
||||
# SimpleHTTPRequestHandler.finish(self)
|
||||
# except (socket.error, TypeError) as err:
|
||||
# self.log_message("finish(): Exception: in SimpleHTTPRequestHandler.finish(): %s" % str(err.args))
|
||||
|
||||
# def handle(self):
|
||||
# #needed when wfile is used, or when self.close_connection is not used
|
||||
# #
|
||||
# #catch errors in SimpleHTTPRequestHandler.handle() after socket disappeared
|
||||
# #due to loss of network connection
|
||||
# try:
|
||||
# SimpleHTTPRequestHandler.handle(self)
|
||||
# except (socket.error, TypeError) as err:
|
||||
# self.log_message("handle(): Exception: in SimpleHTTPRequestHandler.handle(): %s" % str(err.args))
|
||||
|
||||
def checkAuthentication(self):
|
||||
auth = self.headers.get('Authorization')
|
||||
if auth != "Basic %s" % self.server.auth:
|
||||
self.send_response(401)
|
||||
self.send_header("WWW-Authenticate", 'Basic realm="Plugwise"')
|
||||
self.end_headers();
|
||||
return False
|
||||
return True
|
||||
|
||||
def do_GET(self):
|
||||
# if self.server.auth and not self.checkAuthentication():
|
||||
# return
|
||||
if self.headers.get("Upgrade", None) == "websocket":
|
||||
self._handshake()
|
||||
#This handler is in websocket mode now.
|
||||
#do_GET only returns after client close or socket error.
|
||||
self._read_messages()
|
||||
else:
|
||||
self.do_GET_HTTP()
|
||||
|
||||
def _read_messages(self):
|
||||
while self.connected == True:
|
||||
try:
|
||||
self._read_next_message()
|
||||
except (socket.error, WebSocketError), e:
|
||||
#websocket content error, time-out or disconnect.
|
||||
self.log_message("RCV: Close connection: Socket Error %s" % str(e.args))
|
||||
self._ws_close()
|
||||
except Exception as err:
|
||||
#unexpected error in websocket connection.
|
||||
self.log_error("RCV: Exception: in _read_messages: %s" % str(err.args))
|
||||
self._ws_close()
|
||||
|
||||
def _read_next_message(self):
|
||||
#self.rfile.read(n) is blocking.
|
||||
#it returns however immediately when the socket is closed.
|
||||
try:
|
||||
self.opcode = ord(self.rfile.read(1)) & 0x0F
|
||||
length = ord(self.rfile.read(1)) & 0x7F
|
||||
if length == 126:
|
||||
length = struct.unpack(">H", self.rfile.read(2))[0]
|
||||
elif length == 127:
|
||||
length = struct.unpack(">Q", self.rfile.read(8))[0]
|
||||
masks = [ord(byte) for byte in self.rfile.read(4)]
|
||||
decoded = ""
|
||||
for char in self.rfile.read(length):
|
||||
decoded += chr(ord(char) ^ masks[len(decoded) % 4])
|
||||
self._on_message(decoded)
|
||||
except (struct.error, TypeError) as e:
|
||||
#catch exceptions from ord() and struct.unpack()
|
||||
if self.connected:
|
||||
raise WebSocketError("Websocket read aborted while listening")
|
||||
else:
|
||||
#the socket was closed while waiting for input
|
||||
self.log_error("RCV: _read_next_message aborted after closed connection")
|
||||
pass
|
||||
|
||||
def _send_message(self, opcode, message):
|
||||
try:
|
||||
#use of self.wfile.write gives socket exception after socket is closed. Avoid.
|
||||
self.request.send(chr(0x80 + opcode))
|
||||
length = len(message)
|
||||
if length <= 125:
|
||||
self.request.send(chr(length))
|
||||
elif length >= 126 and length <= 65535:
|
||||
self.request.send(chr(126))
|
||||
self.request.send(struct.pack(">H", length))
|
||||
else:
|
||||
self.request.send(chr(127))
|
||||
self.request.send(struct.pack(">Q", length))
|
||||
if length > 0:
|
||||
self.request.send(message)
|
||||
except socket.error, e:
|
||||
#websocket content error, time-out or disconnect.
|
||||
self.log_message("SND: Close connection: Socket Error %s" % str(e.args))
|
||||
self._ws_close()
|
||||
except Exception as err:
|
||||
#unexpected error in websocket connection.
|
||||
self.log_error("SND: Exception: in _send_message: %s" % str(err.args))
|
||||
self._ws_close()
|
||||
|
||||
def _handshake(self):
|
||||
headers=self.headers
|
||||
if headers.get("Upgrade", None) != "websocket":
|
||||
return
|
||||
key = headers['Sec-WebSocket-Key']
|
||||
digest = b64encode(sha1(key + self._ws_GUID).hexdigest().decode('hex'))
|
||||
self.send_response(101, 'Switching Protocols')
|
||||
self.send_header('Upgrade', 'websocket')
|
||||
self.send_header('Connection', 'Upgrade')
|
||||
self.send_header('Sec-WebSocket-Accept', str(digest))
|
||||
self.end_headers()
|
||||
self.connected = True
|
||||
#self.close_connection = 0
|
||||
self.on_ws_connected()
|
||||
|
||||
def _ws_close(self):
|
||||
#avoid closing a single socket two time for send and receive.
|
||||
self.mutex.acquire()
|
||||
try:
|
||||
if self.connected:
|
||||
self.connected = False
|
||||
#Terminate BaseHTTPRequestHandler.handle() loop:
|
||||
self.close_connection = 1
|
||||
#send close and ignore exceptions. An error may already have occurred.
|
||||
try:
|
||||
self._send_close()
|
||||
except:
|
||||
pass
|
||||
self.on_ws_closed()
|
||||
else:
|
||||
self.log_message("_ws_close websocket in closed state. Ignore.")
|
||||
pass
|
||||
finally:
|
||||
self.mutex.release()
|
||||
|
||||
def _on_message(self, message):
|
||||
#self.log_message("_on_message: opcode: %02X msg: %s" % (self.opcode, message))
|
||||
|
||||
# close
|
||||
if self.opcode == self._opcode_close:
|
||||
self.connected = False
|
||||
#Terminate BaseHTTPRequestHandler.handle() loop:
|
||||
self.close_connection = 1
|
||||
try:
|
||||
self._send_close()
|
||||
except:
|
||||
pass
|
||||
self.on_ws_closed()
|
||||
# ping
|
||||
elif self.opcode == self._opcode_ping:
|
||||
_send_message(self._opcode_pong, message)
|
||||
# pong
|
||||
elif self.opcode == self._opcode_pong:
|
||||
pass
|
||||
# data
|
||||
elif (self.opcode == self._opcode_continu or
|
||||
self.opcode == self._opcode_text or
|
||||
self.opcode == self._opcode_binary):
|
||||
self.on_ws_message(message)
|
||||
|
||||
def _send_close(self):
|
||||
#Dedicated _send_close allows for catch all exception handling
|
||||
msg = bytearray()
|
||||
msg.append(0x80 + self._opcode_close)
|
||||
msg.append(0x00)
|
||||
self.request.send(msg)
|
||||
@@ -1,648 +0,0 @@
|
||||
'''
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Dave P.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
'''
|
||||
|
||||
import SocketServer
|
||||
import hashlib
|
||||
import base64
|
||||
import socket
|
||||
import struct
|
||||
import ssl
|
||||
import time
|
||||
import sys
|
||||
import errno
|
||||
import logging
|
||||
from BaseHTTPServer import BaseHTTPRequestHandler
|
||||
from StringIO import StringIO
|
||||
from select import select
|
||||
|
||||
|
||||
class HTTPRequest(BaseHTTPRequestHandler):
|
||||
def __init__(self, request_text):
|
||||
self.rfile = StringIO(request_text)
|
||||
self.raw_requestline = self.rfile.readline()
|
||||
self.error_code = self.error_message = None
|
||||
self.parse_request()
|
||||
|
||||
|
||||
class WebSocket(object):
|
||||
|
||||
handshakeStr = (
|
||||
"HTTP/1.1 101 Switching Protocols\r\n"
|
||||
"Upgrade: WebSocket\r\n"
|
||||
"Connection: Upgrade\r\n"
|
||||
"Sec-WebSocket-Accept: %(acceptstr)s\r\n\r\n"
|
||||
)
|
||||
|
||||
hixiehandshakedStr = (
|
||||
"HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
|
||||
"Upgrade: WebSocket\r\n"
|
||||
"Connection: Upgrade\r\n"
|
||||
"Sec-WebSocket-Origin: %(origin)s\r\n"
|
||||
"Sec-WebSocket-Location: %(type)s://%(host)s%(location)s\r\n\r\n"
|
||||
)
|
||||
|
||||
hixie75handshakedStr = (
|
||||
"HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
|
||||
"Upgrade: WebSocket\r\n"
|
||||
"Connection: Upgrade\r\n"
|
||||
"WebSocket-Origin: %(origin)s\r\n"
|
||||
"WebSocket-Location: %(type)s://%(host)s%(location)s\r\n\r\n"
|
||||
)
|
||||
GUIDStr = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
|
||||
|
||||
STREAM = 0x0
|
||||
TEXT = 0x1
|
||||
BINARY = 0x2
|
||||
CLOSE = 0x8
|
||||
PING = 0x9
|
||||
PONG = 0xA
|
||||
|
||||
HEADERB1 = 1
|
||||
HEADERB2 = 3
|
||||
LENGTHSHORT = 4
|
||||
LENGTHLONG = 5
|
||||
MASK = 6
|
||||
PAYLOAD = 7
|
||||
|
||||
def __init__(self, server, sock, address):
|
||||
self.server = server
|
||||
self.client = sock
|
||||
self.address = address
|
||||
|
||||
self.handshaked = False
|
||||
self.headerbuffer = ''
|
||||
self.readdraftkey = False
|
||||
self.draftkey = ''
|
||||
self.headertoread = 2048
|
||||
self.hixie76 = False
|
||||
|
||||
self.fin = 0
|
||||
self.data = None
|
||||
self.opcode = 0
|
||||
self.hasmask = 0
|
||||
self.maskarray = None
|
||||
self.length = 0
|
||||
self.lengtharray = None
|
||||
self.index = 0
|
||||
self.request = None
|
||||
self.usingssl = False
|
||||
|
||||
self.state = self.HEADERB1
|
||||
|
||||
# restrict the size of header and payload for security reasons
|
||||
self.maxheader = 65536
|
||||
self.maxpayload = 4194304
|
||||
|
||||
def close(self):
|
||||
self.client.close()
|
||||
self.state = self.HEADERB1
|
||||
self.hasmask = False
|
||||
self.handshaked = False
|
||||
self.readdraftkey = False
|
||||
self.hixie76 = False
|
||||
self.headertoread = 2048
|
||||
self.headerbuffer = ''
|
||||
self.data = ''
|
||||
|
||||
|
||||
def handleMessage(self):
|
||||
pass
|
||||
|
||||
def handleConnected(self):
|
||||
pass
|
||||
|
||||
def handleClose(self):
|
||||
pass
|
||||
|
||||
def handlePacket(self):
|
||||
# close
|
||||
if self.opcode == self.CLOSE:
|
||||
self.sendClose()
|
||||
raise Exception("received client close")
|
||||
# ping
|
||||
elif self.opcode == self.PING:
|
||||
pass
|
||||
|
||||
# pong
|
||||
elif self.opcode == self.PONG:
|
||||
pass
|
||||
|
||||
# data
|
||||
elif self.opcode == self.STREAM or self.opcode == self.TEXT or self.opcode == self.BINARY:
|
||||
self.handleMessage()
|
||||
|
||||
|
||||
def handleData(self):
|
||||
|
||||
# do the HTTP header and handshake
|
||||
if self.handshaked is False:
|
||||
|
||||
data = self.client.recv(self.headertoread)
|
||||
|
||||
if data:
|
||||
# accumulate
|
||||
self.headerbuffer += data
|
||||
|
||||
if len(self.headerbuffer) >= self.maxheader:
|
||||
raise Exception('header exceeded allowable size')
|
||||
|
||||
# we need to read the entire 8 bytes of after the HTTP header, ensure we do
|
||||
if self.readdraftkey is True:
|
||||
self.draftkey += self.headerbuffer
|
||||
read = self.headertoread - len(self.headerbuffer)
|
||||
|
||||
if read != 0:
|
||||
self.headertoread = read
|
||||
else:
|
||||
# complete hixie76 handshake
|
||||
self.handshake_hixie76()
|
||||
|
||||
# indicates end of HTTP header
|
||||
elif '\r\n\r\n' in self.headerbuffer:
|
||||
self.request = HTTPRequest(self.headerbuffer)
|
||||
# hixie handshake
|
||||
if self.request.headers.has_key('Sec-WebSocket-Key1'.lower()) and self.request.headers.has_key('Sec-WebSocket-Key2'.lower()):
|
||||
# check if we have the key in our buffer
|
||||
index = self.headerbuffer.find('\r\n\r\n') + 4
|
||||
# determine how much of the 8 byte key we have
|
||||
read = len(self.headerbuffer) - index
|
||||
# do we have all the 8 bytes we need?
|
||||
if read < 8:
|
||||
self.headertoread = 8 - read
|
||||
self.readdraftkey = True
|
||||
if read > 0:
|
||||
self.draftkey += self.headerbuffer[index:index+read]
|
||||
|
||||
else:
|
||||
# get the key
|
||||
self.draftkey += self.headerbuffer[index:index+8]
|
||||
# complete hixie handshake
|
||||
self.handshake_hixie76()
|
||||
|
||||
# handshake rfc 6455
|
||||
elif self.request.headers.has_key('Sec-WebSocket-Key'.lower()):
|
||||
key = self.request.headers['Sec-WebSocket-Key'.lower()]
|
||||
hStr = self.handshakeStr % { 'acceptstr' : base64.b64encode(hashlib.sha1(key + self.GUIDStr).digest()) }
|
||||
self.sendBuffer(hStr)
|
||||
self.handshaked = True
|
||||
self.headerbuffer = ''
|
||||
|
||||
try:
|
||||
self.handleConnected()
|
||||
except:
|
||||
pass
|
||||
# hixie 75
|
||||
else:
|
||||
self.handshake_hixie75()
|
||||
#raise Exception('Sec-WebSocket-Key does not exist')
|
||||
|
||||
# remote connection has been closed
|
||||
else:
|
||||
raise Exception("remote socket closed")
|
||||
|
||||
# else do normal data
|
||||
else:
|
||||
data = self.client.recv(2048)
|
||||
if data:
|
||||
for val in data:
|
||||
if self.hixie76 is False:
|
||||
self.parseMessage(ord(val))
|
||||
else:
|
||||
self.parseMessage_hixie76(ord(val))
|
||||
else:
|
||||
raise Exception("remote socket closed")
|
||||
|
||||
|
||||
def handshake_hixie75(self):
|
||||
typestr = 'ws'
|
||||
if self.usingssl is True:
|
||||
typestr = 'wss'
|
||||
|
||||
response = self.hixie75handshakedStr % { 'type' : typestr, 'origin' : self.request.headers['Origin'.lower()], 'host' : self.request.headers['Host'.lower()], 'location' : self.request.path }
|
||||
|
||||
self.sendBuffer(response)
|
||||
|
||||
|
||||
self.handshaked = True
|
||||
self.headerbuffer = ''
|
||||
self.hixie76 = True
|
||||
|
||||
try:
|
||||
self.handleConnected()
|
||||
except:
|
||||
pass
|
||||
|
||||
def handshake_hixie76(self):
|
||||
|
||||
k1 = self.request.headers['Sec-WebSocket-Key1'.lower()]
|
||||
k2 = self.request.headers['Sec-WebSocket-Key2'.lower()]
|
||||
|
||||
spaces1 = k1.count(" ")
|
||||
spaces2 = k2.count(" ")
|
||||
num1 = int("".join([c for c in k1 if c.isdigit()])) / spaces1
|
||||
num2 = int("".join([c for c in k2 if c.isdigit()])) / spaces2
|
||||
|
||||
key = ''
|
||||
key += struct.pack('>I', num1)
|
||||
key += struct.pack('>I', num2)
|
||||
key += self.draftkey
|
||||
|
||||
typestr = 'ws'
|
||||
if self.usingssl is True:
|
||||
typestr = 'wss'
|
||||
|
||||
response = self.hixiehandshakedStr % { 'type' : typestr, 'origin' : self.request.headers['Origin'.lower()], 'host' : self.request.headers['Host'.lower()], 'location' : self.request.path }
|
||||
|
||||
self.sendBuffer(response)
|
||||
self.sendBuffer(hashlib.md5(key).digest())
|
||||
|
||||
self.handshaked = True
|
||||
self.hixie76 = True
|
||||
self.headerbuffer = ''
|
||||
|
||||
try:
|
||||
self.handleConnected()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def sendClose(self):
|
||||
|
||||
msg = bytearray()
|
||||
if self.hixie76 is False:
|
||||
msg.append(0x88)
|
||||
msg.append(0x00)
|
||||
self.sendBuffer(msg)
|
||||
else:
|
||||
pass
|
||||
|
||||
def sendBuffer(self, buff):
|
||||
size = len(buff)
|
||||
tosend = size
|
||||
index = 0
|
||||
|
||||
while tosend > 0:
|
||||
try:
|
||||
# i should be able to send a bytearray
|
||||
sent = self.client.send(str(buff[index:size]))
|
||||
if sent == 0:
|
||||
raise RuntimeError("socket connection broken")
|
||||
|
||||
index += sent
|
||||
tosend -= sent
|
||||
|
||||
except socket.error as e:
|
||||
# if we have full buffers then wait for them to drain and try again
|
||||
if e.errno == errno.EAGAIN:
|
||||
time.sleep(0.001)
|
||||
else:
|
||||
raise e
|
||||
|
||||
|
||||
#if s is a string then websocket TEXT is sent else BINARY
|
||||
def sendMessage(self, s):
|
||||
|
||||
if self.hixie76 is False:
|
||||
|
||||
header = bytearray()
|
||||
isString = isinstance(s, str)
|
||||
|
||||
if isString is True:
|
||||
header.append(0x81)
|
||||
else:
|
||||
header.append(0x82)
|
||||
|
||||
b2 = 0
|
||||
length = len(s)
|
||||
|
||||
if length <= 125:
|
||||
b2 |= length
|
||||
header.append(b2)
|
||||
|
||||
elif length >= 126 and length <= 65535:
|
||||
b2 |= 126
|
||||
header.append(b2)
|
||||
header.extend(struct.pack("!H", length))
|
||||
|
||||
else:
|
||||
b2 |= 127
|
||||
header.append(b2)
|
||||
header.extend(struct.pack("!Q", length))
|
||||
|
||||
if length > 0:
|
||||
self.sendBuffer(header + s)
|
||||
else:
|
||||
self.sendBuffer(header)
|
||||
header = None
|
||||
|
||||
else:
|
||||
msg = bytearray()
|
||||
msg.append(0)
|
||||
if len(s) > 0:
|
||||
msg.extend(str(s).encode("UTF8"))
|
||||
msg.append(0xFF)
|
||||
|
||||
self.sendBuffer(msg)
|
||||
msg = None
|
||||
|
||||
|
||||
def parseMessage_hixie76(self, byte):
|
||||
|
||||
if self.state == self.HEADERB1:
|
||||
if byte == 0:
|
||||
self.state = self.PAYLOAD
|
||||
self.data = bytearray()
|
||||
|
||||
elif self.state == self.PAYLOAD:
|
||||
if byte == 0xFF:
|
||||
self.opcode = 1
|
||||
self.length = len(self.data)
|
||||
try:
|
||||
self.handlePacket()
|
||||
finally:
|
||||
self.data = None
|
||||
self.state = self.HEADERB1
|
||||
else :
|
||||
self.data.append(byte)
|
||||
# if length exceeds allowable size then we except and remove the connection
|
||||
if len(self.data) >= self.maxpayload:
|
||||
raise Exception('payload exceeded allowable size')
|
||||
|
||||
|
||||
def parseMessage(self, byte):
|
||||
# read in the header
|
||||
if self.state == self.HEADERB1:
|
||||
# fin
|
||||
self.fin = (byte & 0x80)
|
||||
# get opcode
|
||||
self.opcode = (byte & 0x0F)
|
||||
|
||||
self.state = self.HEADERB2
|
||||
|
||||
elif self.state == self.HEADERB2:
|
||||
mask = byte & 0x80
|
||||
length = byte & 0x7F
|
||||
|
||||
if mask == 128:
|
||||
self.hasmask = True
|
||||
else:
|
||||
self.hasmask = False
|
||||
|
||||
if length <= 125:
|
||||
self.length = length
|
||||
|
||||
# if we have a mask we must read it
|
||||
if self.hasmask is True:
|
||||
self.maskarray = bytearray()
|
||||
self.state = self.MASK
|
||||
else:
|
||||
# if there is no mask and no payload we are done
|
||||
if self.length <= 0:
|
||||
try:
|
||||
self.handlePacket()
|
||||
finally:
|
||||
self.state = self.HEADERB1
|
||||
self.data = None
|
||||
|
||||
# we have no mask and some payload
|
||||
else:
|
||||
self.index = 0
|
||||
self.data = bytearray()
|
||||
self.state = self.PAYLOAD
|
||||
|
||||
elif length == 126:
|
||||
self.lengtharray = bytearray()
|
||||
self.state = self.LENGTHSHORT
|
||||
|
||||
elif length == 127:
|
||||
self.lengtharray = bytearray()
|
||||
self.state = self.LENGTHLONG
|
||||
|
||||
|
||||
elif self.state == self.LENGTHSHORT:
|
||||
self.lengtharray.append(byte)
|
||||
|
||||
if len(self.lengtharray) > 2:
|
||||
raise Exception('short length exceeded allowable size')
|
||||
|
||||
if len(self.lengtharray) == 2:
|
||||
self.length = struct.unpack_from('!H', str(self.lengtharray))[0]
|
||||
|
||||
if self.hasmask is True:
|
||||
self.maskarray = bytearray()
|
||||
self.state = self.MASK
|
||||
else:
|
||||
# if there is no mask and no payload we are done
|
||||
if self.length <= 0:
|
||||
try:
|
||||
self.handlePacket()
|
||||
finally:
|
||||
self.state = self.HEADERB1
|
||||
self.data = None
|
||||
|
||||
# we have no mask and some payload
|
||||
else:
|
||||
self.index = 0
|
||||
self.data = bytearray()
|
||||
self.state = self.PAYLOAD
|
||||
|
||||
elif self.state == self.LENGTHLONG:
|
||||
|
||||
self.lengtharray.append(byte)
|
||||
|
||||
if len(self.lengtharray) > 8:
|
||||
raise Exception('long length exceeded allowable size')
|
||||
|
||||
if len(self.lengtharray) == 8:
|
||||
self.length = struct.unpack_from('!Q', str(self.lengtharray))[0]
|
||||
|
||||
if self.hasmask is True:
|
||||
self.maskarray = bytearray()
|
||||
self.state = self.MASK
|
||||
else:
|
||||
# if there is no mask and no payload we are done
|
||||
if self.length <= 0:
|
||||
try:
|
||||
self.handlePacket()
|
||||
finally:
|
||||
self.state = self.HEADERB1
|
||||
self.data = None
|
||||
|
||||
# we have no mask and some payload
|
||||
else:
|
||||
self.index = 0
|
||||
self.data = bytearray()
|
||||
self.state = self.PAYLOAD
|
||||
|
||||
# MASK STATE
|
||||
elif self.state == self.MASK:
|
||||
self.maskarray.append(byte)
|
||||
|
||||
if len(self.maskarray) > 4:
|
||||
raise Exception('mask exceeded allowable size')
|
||||
|
||||
if len(self.maskarray) == 4:
|
||||
# if there is no mask and no payload we are done
|
||||
if self.length <= 0:
|
||||
try:
|
||||
self.handlePacket()
|
||||
finally:
|
||||
self.state = self.HEADERB1
|
||||
self.data = None
|
||||
|
||||
# we have no mask and some payload
|
||||
else:
|
||||
self.index = 0
|
||||
self.data = bytearray()
|
||||
self.state = self.PAYLOAD
|
||||
|
||||
# PAYLOAD STATE
|
||||
elif self.state == self.PAYLOAD:
|
||||
if self.hasmask is True:
|
||||
self.data.append( byte ^ self.maskarray[self.index % 4] )
|
||||
else:
|
||||
self.data.append( byte )
|
||||
|
||||
# if length exceeds allowable size then we except and remove the connection
|
||||
if len(self.data) >= self.maxpayload:
|
||||
raise Exception('payload exceeded allowable size')
|
||||
|
||||
# check if we have processed length bytes; if so we are done
|
||||
if (self.index+1) == self.length:
|
||||
try:
|
||||
self.handlePacket()
|
||||
finally:
|
||||
self.state = self.HEADERB1
|
||||
self.data = None
|
||||
else:
|
||||
self.index += 1
|
||||
|
||||
|
||||
class SimpleWebSocketServer(object):
|
||||
def __init__(self, host, port, websocketclass):
|
||||
self.websocketclass = websocketclass
|
||||
self.serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.serversocket.bind((host, port))
|
||||
self.serversocket.listen(5)
|
||||
self.connections = {}
|
||||
self.listeners = [self.serversocket]
|
||||
|
||||
|
||||
def decorateSocket(self, sock):
|
||||
return sock
|
||||
|
||||
def constructWebSocket(self, sock, address):
|
||||
return self.websocketclass(self, sock, address)
|
||||
|
||||
def close(self):
|
||||
self.serversocket.close()
|
||||
|
||||
for conn in self.connections.itervalues():
|
||||
try:
|
||||
conn.handleClose()
|
||||
except:
|
||||
pass
|
||||
|
||||
conn.close()
|
||||
|
||||
|
||||
def serveforever(self):
|
||||
while True:
|
||||
try:
|
||||
rList, wList, xList = select(self.listeners, [], self.listeners, 1)
|
||||
|
||||
for ready in rList:
|
||||
if ready == self.serversocket:
|
||||
try:
|
||||
sock, address = self.serversocket.accept()
|
||||
newsock = self.decorateSocket(sock)
|
||||
newsock.setblocking(0)
|
||||
fileno = newsock.fileno()
|
||||
self.listeners.append(fileno)
|
||||
self.connections[fileno] = self.constructWebSocket(newsock, address)
|
||||
|
||||
except Exception as n:
|
||||
|
||||
#logging.debug(str(address) + ' ' + str(n))
|
||||
|
||||
if sock is not None:
|
||||
sock.close()
|
||||
else:
|
||||
client = self.connections[ready]
|
||||
|
||||
try:
|
||||
client.handleData()
|
||||
|
||||
except Exception as n:
|
||||
|
||||
#logging.debug(str(client.address) + ' ' + str(n))
|
||||
|
||||
try:
|
||||
client.handleClose()
|
||||
except:
|
||||
pass
|
||||
|
||||
client.close()
|
||||
|
||||
del self.connections[ready]
|
||||
self.listeners.remove(ready)
|
||||
|
||||
for failed in xList:
|
||||
if failed == self.serversocket:
|
||||
self.close()
|
||||
raise Exception("server socket failed")
|
||||
else:
|
||||
client = self.connections[failed]
|
||||
|
||||
try:
|
||||
client.handleClose()
|
||||
except:
|
||||
pass
|
||||
|
||||
client.close()
|
||||
|
||||
del self.connections[failed]
|
||||
self.listeners.remove(failed)
|
||||
except:
|
||||
break
|
||||
|
||||
class SimpleSSLWebSocketServer(SimpleWebSocketServer):
|
||||
|
||||
def __init__(self, host, port, websocketclass, certfile, keyfile, version = ssl.PROTOCOL_TLSv1):
|
||||
|
||||
SimpleWebSocketServer.__init__(self, host, port, websocketclass)
|
||||
|
||||
self.cerfile = certfile
|
||||
self.keyfile = keyfile
|
||||
self.version = version
|
||||
|
||||
def close(self):
|
||||
super(SimpleSSLWebSocketServer, self).close()
|
||||
|
||||
def decorateSocket(self, sock):
|
||||
sslsock = ssl.wrap_socket(sock,
|
||||
server_side=True,
|
||||
certfile=self.cerfile,
|
||||
keyfile=self.keyfile,
|
||||
ssl_version=self.version)
|
||||
return sslsock
|
||||
|
||||
def constructWebSocket(self, sock, address):
|
||||
ws = self.websocketclass(self, sock, address)
|
||||
ws.usingssl = True
|
||||
return ws
|
||||
|
||||
def serveforever(self):
|
||||
super(SimpleSSLWebSocketServer, self).serveforever()
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ class fileserver(Controller):
|
||||
self.handler.send_header('Content-type', 'text/html')
|
||||
self.handler.end_headers()
|
||||
respuesta = f.read()
|
||||
respuesta = respuesta.replace("{$WSPORT}", str(config.get_setting("websocket.port")))
|
||||
self.handler.wfile.write(respuesta)
|
||||
f.close()
|
||||
|
||||
|
||||
@@ -33,7 +33,10 @@ class html(Controller):
|
||||
self.platformtools = platform(self)
|
||||
self.data = {}
|
||||
if self.handler:
|
||||
self.client_ip = handler.client.getpeername()[0]
|
||||
if hasattr(handler, "client"):
|
||||
self.client_ip = handler.client.getpeername()[0]
|
||||
else:
|
||||
self.client_ip = handler.client_address[0]
|
||||
self.send_message({"action": "connect",
|
||||
"data": {"version": "Alfa %s" % version,
|
||||
"date": "--/--/----"}})
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<script type="text/javascript" src="/media/js/socket.js"></script>
|
||||
<script type="text/javascript" src="/media/js/utils.js"></script>
|
||||
<script type="text/javascript">
|
||||
websocket_host = 'ws://' + window.location.hostname + ':{$WSPORT}'
|
||||
websocket_host = 'ws://' + window.location.host
|
||||
</script>
|
||||
</head>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user