Last active
January 15, 2019 05:29
-
-
Save StoneMoe/3c95184a29343a89a1ac8817ef486294 to your computer and use it in GitHub Desktop.
Monkey patch for Python-EngineIO JSONP-Polling support
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# For Python-EngineIO 2.0.2 | |
def engine_io_patcher(): | |
from engineio.payload import Payload as eio_Payload | |
from engineio.server import Server as eio_Server | |
original_handle_request = eio_Server.handle_request | |
def new_eioserv__ok(self, packets=None, headers=None, b64=False, jsonp_seq=None): | |
"""response generator.""" | |
if packets is not None: | |
if headers is None: | |
headers = [] | |
if b64: | |
headers += [('Content-Type', 'text/plain; charset=UTF-8')] | |
else: | |
headers += [('Content-Type', 'application/octet-stream')] | |
return {'status': '200 OK', | |
'headers': headers, | |
'response': eio_Payload(packets=packets).encode(b64=b64, jsonp_seq=jsonp_seq)} | |
else: | |
return {'status': '200 OK', | |
'headers': [('Content-Type', 'text/plain')], | |
'response': b'OK'} | |
def new_eiopayload_decode(self, encoded_payload): | |
"""Decode a transmitted payload.""" | |
import six | |
from engineio import packet | |
self.packets = [] | |
while encoded_payload: | |
if encoded_payload.startswith(b'd='): # is form submit | |
encoded_payload = encoded_payload[2:] | |
encoded_payload = parse.unquote_to_bytes(encoded_payload.decode('utf-8')) | |
if six.byte2int(encoded_payload[0:1]) <= 1: | |
packet_len = 0 | |
i = 1 | |
while six.byte2int(encoded_payload[i:i + 1]) != 255: | |
packet_len = packet_len * 10 + six.byte2int(encoded_payload[i:i + 1]) | |
i += 1 | |
self.packets.append(packet.Packet(encoded_packet=encoded_payload[i + 1:i + 1 + packet_len])) | |
else: | |
i = encoded_payload.find(b':') | |
if i == -1: | |
raise ValueError('invalid payload') | |
# extracting the packet out of the payload is extremely | |
# inefficient, because the payload needs to be treated as | |
# binary, but the non-binary packets have to be parsed as | |
# unicode. Luckily this complication only applies to long | |
# polling, as the websocket transport sends packets | |
# individually wrapped. | |
packet_len = int(encoded_payload[0:i]) | |
pkt = encoded_payload.decode('utf-8', errors='ignore')[ | |
i + 1: i + 1 + packet_len].encode('utf-8') | |
self.packets.append(packet.Packet(encoded_packet=pkt)) | |
# the engine.io protocol sends the packet length in | |
# utf-8 characters, but we need it in bytes to be able to | |
# jump to the next packet in the payload | |
packet_len = len(pkt) | |
encoded_payload = encoded_payload[i + 1 + packet_len:] | |
def new_eiopayload_encode(self, b64=False, jsonp_seq=None): | |
"""Encode the payload for transmission.""" | |
import six | |
encoded_payload = b'' | |
for pkt in self.packets: | |
encoded_packet = pkt.encode(b64=b64) | |
packet_len = len(encoded_packet) | |
if b64: | |
encoded_payload += str(packet_len).encode('utf-8') + b':' + encoded_packet | |
else: | |
binary_len = b'' | |
while packet_len != 0: | |
binary_len = six.int2byte(packet_len % 10) + binary_len | |
packet_len = int(packet_len / 10) | |
if not pkt.binary: | |
encoded_payload += b'\0' | |
else: | |
encoded_payload += b'\1' | |
encoded_payload += binary_len + b'\xff' + encoded_packet | |
# Patch start | |
if jsonp_seq is not None: | |
return '___eio[%s]("%s");' % (str(jsonp_seq), str(encoded_payload, encoding='utf-8').replace('"', '\\"')) | |
# Patch end | |
return encoded_payload | |
def new_eioserv__handle_connect(self, environ, start_response, transport, b64=False, jsonp_seq=None): | |
"""handshake entry""" | |
from engineio import socket | |
from engineio import packet | |
sid = self._generate_id() | |
s = socket.Socket(self, sid) | |
# Patch start | |
# save jsonp status to engineio::socket instance | |
setattr(s, 'is_jsonp', True if jsonp_seq is not None else False) | |
setattr(s, 'jsonp_seq', jsonp_seq) | |
# Patch end | |
self.sockets[sid] = s | |
pkt = packet.Packet( | |
packet.OPEN, {'sid': sid, | |
'upgrades': self._upgrades(sid, transport), | |
'pingTimeout': int(self.ping_timeout * 1000), | |
'pingInterval': int(self.ping_interval * 1000)}) | |
s.send(pkt) | |
ret = self._trigger_event('connect', sid, environ, run_async=False) | |
if ret is False: | |
del self.sockets[sid] | |
self.logger.warning('Application rejected connection') | |
return self._unauthorized() | |
if transport == 'websocket': | |
ret = s.handle_get_request(environ, start_response) | |
if s.closed: | |
# websocket connection ended, so we are done | |
del self.sockets[sid] | |
return ret | |
else: | |
s.connected = True | |
headers = None | |
if self.cookie: | |
headers = [('Set-Cookie', self.cookie + '=' + sid)] | |
return self._ok(s.poll(), headers=headers, b64=b64, jsonp_seq=jsonp_seq) | |
def new_eioserv_handle_request(self, environ, start_response, patched_call=False): | |
# Patch start | |
# bypass jsonp not supported bad request | |
# handle jsonp request with regular polling code | |
if not patched_call: | |
from six.moves import urllib | |
from engineio.exceptions import EngineIOError | |
method = environ['REQUEST_METHOD'] | |
query = urllib.parse.parse_qs(environ.get('QUERY_STRING', '')) | |
if 'j' in query: | |
sid = query['sid'][0] if 'sid' in query else None | |
b64 = False | |
if 'b64' in query: | |
if query['b64'][0] == "1" or query['b64'][0].lower() == "true": | |
b64 = True | |
if method == 'GET': | |
if sid is None: # Need handshake | |
transport = query.get('transport', ['polling'])[0] | |
if transport != 'polling' and transport != 'websocket': | |
self.logger.warning('Invalid transport %s', transport) | |
r = self._bad_request() | |
else: | |
r = self._handle_connect(environ, start_response, transport, b64, jsonp_seq=int(query['j'][0])) | |
else: | |
if sid not in self.sockets: | |
self.logger.warning('Invalid session %s', sid) | |
r = self._bad_request() | |
else: | |
socket = self._get_socket(sid) | |
try: | |
packets = socket.handle_get_request(environ, start_response) | |
if isinstance(packets, list): | |
r = self._ok(packets, b64=b64, jsonp_seq=int(query['j'][0])) | |
else: | |
r = packets | |
except EngineIOError: | |
if sid in self.sockets: # pragma: no cover | |
self.disconnect(sid) | |
r = self._bad_request() | |
if sid in self.sockets and self.sockets[sid].closed: | |
del self.sockets[sid] | |
elif method == 'POST': | |
if sid is None or sid not in self.sockets: | |
self.logger.warning('Invalid session %s', sid) | |
r = self._bad_request() | |
else: | |
socket = self._get_socket(sid) | |
try: | |
socket.handle_post_request(environ) | |
r = self._ok(jsonp_seq=int(query['j'][0])) | |
except EngineIOError: | |
if sid in self.sockets: # pragma: no cover | |
self.disconnect(sid) | |
r = self._bad_request() | |
except: # pragma: no cover | |
# for any other unexpected errors, we log the error | |
# and keep going | |
self.logger.exception('post request handler error') | |
r = self._ok(jsonp_seq=int(query['j'][0])) | |
else: | |
self.logger.warning('Method %s not supported', method) | |
r = self._method_not_found() | |
if not isinstance(r, dict): | |
return r or [] | |
if self.http_compression and len(r['response']) >= self.compression_threshold: | |
encodings = [e.split(';')[0].strip() for e in environ.get('HTTP_ACCEPT_ENCODING', '').split(',')] | |
for encoding in encodings: | |
if encoding in self.compression_methods: | |
r['response'] = getattr(self, '_' + encoding)(r['response']) | |
r['headers'] += [('Content-Encoding', encoding)] | |
break | |
cors_headers = self._cors_headers(environ) | |
start_response(r['status'], r['headers'] + cors_headers) | |
return [r['response']] | |
else: | |
return original_handle_request(self, environ, start_response) | |
# Patch end | |
eio_Server._ok = new_eioserv__ok | |
eio_Payload.decode = new_eiopayload_decode | |
eio_Payload.encode = new_eiopayload_encode | |
eio_Server.handle_request = new_eioserv_handle_request | |
eio_Server._handle_connect = new_eioserv__handle_connect | |
log('EngineIO JSONP support patch done.') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment