From 29a15cc2ca9f07787095097729950c9564c14b97 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 10 Mar 2020 17:07:42 +0000 Subject: [PATCH 01/16] Added method to return current GET response --- labthings/server/view.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/labthings/server/view.py b/labthings/server/view.py index 17c67bf7..72a177d2 100644 --- a/labthings/server/view.py +++ b/labthings/server/view.py @@ -27,16 +27,14 @@ def __init__(self, *args, **kwargs): # TODO: Inherit from parent LabThing. See original flask_restful implementation self.representations = OrderedDict(DEFAULT_REPRESENTATIONS) - def doc(self): - docs = {"operations": {}} - if hasattr(self, "__apispec__"): - docs.update(self.__apispec__) - - for meth in View.methods: - if hasattr(self, meth) and hasattr(getattr(self, meth), "__apispec__"): - docs["operations"][meth] = {} - docs["operations"][meth] = getattr(self, meth).__apispec__ - return docs + def get_value(self): + get_method = getattr(self, "get", None) # Look for this views GET method + if callable(get_method): # Check it's callable + response = get_method() # pylint: disable=not-callable + if isinstance(response, ResponseBase): # Pluck useful data out of HTTP response + return response.json if response.json else response.data.decode() + else: # Unless somehow an HTTP response isn't returned... + return response def dispatch_request(self, *args, **kwargs): meth = getattr(self, request.method.lower(), None) From 945bf11c0edc596eeefb3054b144b494689ee0bb Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 10 Mar 2020 17:08:42 +0000 Subject: [PATCH 02/16] Split out function to encode JSON with current Flask encoder settings --- labthings/server/representations.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/labthings/server/representations.py b/labthings/server/representations.py index 2395dae2..c91eb5ed 100644 --- a/labthings/server/representations.py +++ b/labthings/server/representations.py @@ -4,8 +4,8 @@ from ..core.utilities import PY3 -def output_json(data, code, headers=None): - """Makes a Flask response with a JSON encoded body""" +def encode_json(data): + """Makes JSON encoded data using the current Flask apps JSON settings""" settings = current_app.config.get("LABTHINGS_JSON", {}) encoder = current_app.json_encoder @@ -21,6 +21,14 @@ def output_json(data, code, headers=None): # see https://github.com/mitsuhiko/flask/pull/1262 dumped = dumps(data, cls=encoder, **settings) + "\n" + return dumped + + +def output_json(data, code, headers=None): + """Makes a Flask response with a JSON encoded body""" + + dumped = encode_json(data) + "\n" + resp = make_response(dumped, code) resp.headers.extend(headers or {}) return resp From d78246e9f03f1d1875acd91823d66e2681142dc5 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 10 Mar 2020 17:08:55 +0000 Subject: [PATCH 03/16] Added basic property subscriptions via websocket --- labthings/server/decorators.py | 27 +++++++++++++++++++++++++-- labthings/server/labthing.py | 16 ++++++++++++++-- labthings/server/sockets.py | 25 +++++++++++++++++++++++-- 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/labthings/server/decorators.py b/labthings/server/decorators.py index 3dc65ec9..f672d4f9 100644 --- a/labthings/server/decorators.py +++ b/labthings/server/decorators.py @@ -1,6 +1,7 @@ from webargs import flaskparser from functools import wraps, update_wrapper from flask import make_response, abort, request +from werkzeug.wrappers import Response as ResponseBase from http import HTTPStatus from marshmallow.exceptions import ValidationError from collections import Mapping @@ -9,6 +10,7 @@ from .schema import TaskSchema, Schema, FieldSchema from .fields import Field from .view import View +from .find import current_labthing import logging @@ -102,7 +104,7 @@ def ThingAction(viewcls: View): Returns: View: View class with Action spec tags """ - # Pass params to call function attribute for external access + # Update Views API spec update_spec(viewcls, {"tags": ["actions"]}) update_spec(viewcls, {"_groups": ["actions"]}) return viewcls @@ -120,7 +122,28 @@ def ThingProperty(viewcls): Returns: View: View class with Property spec tags """ - # Pass params to call function attribute for external access + + def property_notify(func): + @wraps(func) + def wrapped(*args, **kwargs): + # Call the update function first to update property value + original_response = func(*args, **kwargs) + + # Once updated, then notify all subscribers + subscribers = getattr(current_labthing(), "subscribers", []) + for sub in subscribers: + sub.property_notify(viewcls) + return original_response + + return wrapped + + if hasattr(viewcls, "post") and callable(viewcls.post): + viewcls.post = property_notify(viewcls.post) + + if hasattr(viewcls, "put") and callable(viewcls.put): + viewcls.put = property_notify(viewcls.put) + + # Update Views API spec update_spec(viewcls, {"tags": ["properties"]}) update_spec(viewcls, {"_groups": ["properties"]}) return viewcls diff --git a/labthings/server/labthing.py b/labthings/server/labthing.py index 7796d9fa..8d1938b9 100644 --- a/labthings/server/labthing.py +++ b/labthings/server/labthing.py @@ -10,7 +10,7 @@ from .spec.utilities import get_spec from .spec.td import ThingDescription from .decorators import tag -from .sockets import Sockets +from .sockets import Sockets, SocketSubscriber from .views.extensions import ExtensionList from .views.tasks import TaskList, TaskView @@ -38,6 +38,10 @@ def __init__( self.extensions = {} self.views = [] + self._property_views = {} + self._action_views = {} + + self.subscribers = set() self.endpoints = set() @@ -133,9 +137,15 @@ def _create_base_sockets(self): self.sockets.add_url_rule("/", self._socket_handler) def _socket_handler(self, ws): + wssub = SocketSubscriber(ws) + self.subscribers.add(wssub) + logging.info(f"Added subscriber {wssub}") + logging.debug(list(self.subscribers)) while not ws.closed: message = ws.receive() - ws.send("Web sockets not yet implemented") + self.subscribers.remove(wssub) + logging.info(f"Removed subscriber {wssub}") + logging.debug(list(self.subscribers)) # Device stuff @@ -277,8 +287,10 @@ def _register_view(self, app, view, *urls, endpoint=None, **kwargs): view_groups = view_spec.get("_groups", {}) if "actions" in view_groups: self.thing_description.action(flask_rules, view) + self._action_views[view.endpoint] = view if "properties" in view_groups: self.thing_description.property(flask_rules, view) + self._property_views[view.endpoint] = view # Utilities diff --git a/labthings/server/sockets.py b/labthings/server/sockets.py index e0390b23..7c5d2cdc 100644 --- a/labthings/server/sockets.py +++ b/labthings/server/sockets.py @@ -13,8 +13,8 @@ from werkzeug.routing import Map, Rule from werkzeug.exceptions import NotFound from werkzeug.http import parse_cookie -from flask import request - +from flask import request, current_app +import logging try: from geventwebsocket.gunicorn.workers import GeventWebSocketWorker as Worker @@ -25,6 +25,27 @@ except ImportError: pass +from .representations import encode_json + + +class SocketSubscriber: + def __init__(self, ws): + self.ws = ws + + def property_notify(self, viewcls): + if hasattr(viewcls, "get_value") and callable(viewcls.get_value): + property_value = viewcls().get_value() + else: + property_value = None + + property_name = str(getattr(viewcls, "endpoint", "unknown")) + + response = encode_json( + {"messageType": "propertyStatus", "data": {property_name: property_value},} + ) + + self.ws.send(response) + class SocketMiddleware(object): def __init__(self, wsgi_app, app, socket): From 2abec0a200e3dbeb8afc8549342328be2357578e Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 10 Mar 2020 17:34:25 +0000 Subject: [PATCH 04/16] Run gevent monkey patch on server import --- labthings/server/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/labthings/server/__init__.py b/labthings/server/__init__.py index 1d3f44bd..44dc16f5 100644 --- a/labthings/server/__init__.py +++ b/labthings/server/__init__.py @@ -1 +1,5 @@ EXTENSION_NAME = "flask-labthings" + +from gevent import monkey + +monkey.patch_all() From 3d3366a84ecfd94ae9dfed9a747e94b0ce316a1c Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Thu, 12 Mar 2020 15:10:30 +0000 Subject: [PATCH 05/16] Added default eventlet support --- examples/builder.py | 6 +- labthings/server/labthing.py | 8 +- labthings/server/sockets.py | 140 ------------------- labthings/server/sockets/__init__.py | 2 + labthings/server/sockets/base.py | 89 ++++++++++++ labthings/server/sockets/eventlet.py | 54 +++++++ labthings/server/sockets/gevent.py | 58 ++++++++ labthings/server/wsgi/__init__.py | 1 + labthings/server/wsgi/eventlet.py | 43 ++++++ labthings/server/{wsgi.py => wsgi/gevent.py} | 0 10 files changed, 257 insertions(+), 144 deletions(-) delete mode 100644 labthings/server/sockets.py create mode 100644 labthings/server/sockets/__init__.py create mode 100644 labthings/server/sockets/base.py create mode 100644 labthings/server/sockets/eventlet.py create mode 100644 labthings/server/sockets/gevent.py create mode 100644 labthings/server/wsgi/__init__.py create mode 100644 labthings/server/wsgi/eventlet.py rename labthings/server/{wsgi.py => wsgi/gevent.py} (100%) diff --git a/examples/builder.py b/examples/builder.py index 5301d0fe..ac4822de 100644 --- a/examples/builder.py +++ b/examples/builder.py @@ -16,6 +16,7 @@ def cleanup(): # Create LabThings Flask app app, labthing = create_app( __name__, + prefix="/api", title="My Lab Device API", description="Test LabThing-based API", version="0.1.0", @@ -46,5 +47,8 @@ def cleanup(): if __name__ == "__main__": from labthings.server.wsgi import Server + logger = logging.getLogger() + logger.setLevel(logging.DEBUG) + server = Server(app) - server.run(host="0.0.0.0", port=5000, debug=True) + server.run(host="0.0.0.0", port=5000, debug=False) diff --git a/labthings/server/labthing.py b/labthings/server/labthing.py index 8d1938b9..e9179539 100644 --- a/labthings/server/labthing.py +++ b/labthings/server/labthing.py @@ -10,7 +10,7 @@ from .spec.utilities import get_spec from .spec.td import ThingDescription from .decorators import tag -from .sockets import Sockets, SocketSubscriber +from .sockets import Sockets, SocketSubscriber, socket_handler_loop from .views.extensions import ExtensionList from .views.tasks import TaskList, TaskView @@ -137,12 +137,14 @@ def _create_base_sockets(self): self.sockets.add_url_rule("/", self._socket_handler) def _socket_handler(self, ws): + # Create a socket subscriber wssub = SocketSubscriber(ws) self.subscribers.add(wssub) logging.info(f"Added subscriber {wssub}") logging.debug(list(self.subscribers)) - while not ws.closed: - message = ws.receive() + # Start the socket connection handler loop + socket_handler_loop(ws) + # Remove the subscriber once the loop returns self.subscribers.remove(wssub) logging.info(f"Removed subscriber {wssub}") logging.debug(list(self.subscribers)) diff --git a/labthings/server/sockets.py b/labthings/server/sockets.py deleted file mode 100644 index 7c5d2cdc..00000000 --- a/labthings/server/sockets.py +++ /dev/null @@ -1,140 +0,0 @@ -""" -Copyright (C) 2013 Kenneth Reitz - -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. -""" - -# -*- coding: utf-8 -*- - -from werkzeug.routing import Map, Rule -from werkzeug.exceptions import NotFound -from werkzeug.http import parse_cookie -from flask import request, current_app -import logging - -try: - from geventwebsocket.gunicorn.workers import GeventWebSocketWorker as Worker - from geventwebsocket.handler import WebSocketHandler - from gunicorn.workers.ggevent import PyWSGIHandler - - import gevent -except ImportError: - pass - -from .representations import encode_json - - -class SocketSubscriber: - def __init__(self, ws): - self.ws = ws - - def property_notify(self, viewcls): - if hasattr(viewcls, "get_value") and callable(viewcls.get_value): - property_value = viewcls().get_value() - else: - property_value = None - - property_name = str(getattr(viewcls, "endpoint", "unknown")) - - response = encode_json( - {"messageType": "propertyStatus", "data": {property_name: property_value},} - ) - - self.ws.send(response) - - -class SocketMiddleware(object): - def __init__(self, wsgi_app, app, socket): - self.ws = socket - self.app = app - self.wsgi_app = wsgi_app - - def __call__(self, environ, start_response): - adapter = self.ws.url_map.bind_to_environ(environ) - try: - handler, values = adapter.match() - environment = environ["wsgi.websocket"] - cookie = None - if "HTTP_COOKIE" in environ: - cookie = parse_cookie(environ["HTTP_COOKIE"]) - - with self.app.app_context(): - with self.app.request_context(environ): - # add cookie to the request to have correct session handling - request.cookie = cookie - - handler(environment, **values) - return [] - except (NotFound, KeyError): - return self.wsgi_app(environ, start_response) - - -class Sockets(object): - def __init__(self, app=None): - #: Compatibility with 'Flask' application. - #: The :class:`~werkzeug.routing.Map` for this instance. You can use - #: this to change the routing converters after the class was created - #: but before any routes are connected. - self.url_map = Map() - - #: Compatibility with 'Flask' application. - #: All the attached blueprints in a dictionary by name. Blueprints - #: can be attached multiple times so this dictionary does not tell - #: you how often they got attached. - self.blueprints = {} - self._blueprint_order = [] - - if app: - self.init_app(app) - - def init_app(self, app): - app.wsgi_app = SocketMiddleware(app.wsgi_app, app, self) - - def route(self, rule, **options): - def decorator(view_func): - endpoint = options.pop("endpoint", None) - self.add_url_rule(rule, view_func, **options) - return view_func - - return decorator - - def add_url_rule(self, rule, view_func, **options): - self.url_map.add(Rule(rule, endpoint=view_func)) - - def register_blueprint(self, blueprint, **options): - """ - Registers a blueprint for web sockets like for 'Flask' application. - Decorator :meth:`~flask.app.setupmethod` is not applied, because it - requires ``debug`` and ``_got_first_request`` attributes to be defined. - """ - first_registration = False - - if blueprint.name in self.blueprints: - assert self.blueprints[blueprint.name] is blueprint, ( - "A blueprint's name collision occurred between %r and " - '%r. Both share the same name "%s". Blueprints that ' - "are created on the fly need unique names." - % (blueprint, self.blueprints[blueprint.name], blueprint.name) - ) - else: - self.blueprints[blueprint.name] = blueprint - self._blueprint_order.append(blueprint) - first_registration = True - - blueprint.register(self, options, first_registration) - - -# CLI sugar. -if "Worker" in locals() and "PyWSGIHandler" in locals() and "gevent" in locals(): - - class GunicornWebSocketHandler(PyWSGIHandler, WebSocketHandler): - def log_request(self): - if "101" not in self.status: - super(GunicornWebSocketHandler, self).log_request() - - Worker.wsgi_handler = GunicornWebSocketHandler - worker = Worker diff --git a/labthings/server/sockets/__init__.py b/labthings/server/sockets/__init__.py new file mode 100644 index 00000000..715d714a --- /dev/null +++ b/labthings/server/sockets/__init__.py @@ -0,0 +1,2 @@ +from .base import SocketSubscriber +from .eventlet import Sockets, socket_handler_loop diff --git a/labthings/server/sockets/base.py b/labthings/server/sockets/base.py new file mode 100644 index 00000000..9c3fb369 --- /dev/null +++ b/labthings/server/sockets/base.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- + +from werkzeug.routing import Map, Rule +from werkzeug.exceptions import NotFound +from werkzeug.http import parse_cookie +from flask import request, current_app +import logging +from abc import ABC, abstractmethod + +from ..representations import encode_json + + +class SocketSubscriber: + def __init__(self, ws): + self.ws = ws + + def property_notify(self, viewcls): + if hasattr(viewcls, "get_value") and callable(viewcls.get_value): + property_value = viewcls().get_value() + else: + property_value = None + + property_name = str(getattr(viewcls, "endpoint", "unknown")) + + response = encode_json( + {"messageType": "propertyStatus", "data": {property_name: property_value},} + ) + + self.ws.send(response) + + +class BaseSockets(ABC): + def __init__(self, app=None): + #: Compatibility with 'Flask' application. + #: The :class:`~werkzeug.routing.Map` for this instance. You can use + #: this to change the routing converters after the class was created + #: but before any routes are connected. + self.url_map = Map() + + #: Compatibility with 'Flask' application. + #: All the attached blueprints in a dictionary by name. Blueprints + #: can be attached multiple times so this dictionary does not tell + #: you how often they got attached. + self.blueprints = {} + self._blueprint_order = [] + + if app: + self.init_app(app) + + @abstractmethod + def init_app(self, app): + pass + + def route(self, rule, **options): + def decorator(view_func): + options.pop("endpoint", None) + self.add_url_rule(rule, view_func, **options) + return view_func + + return decorator + + def add_url_rule(self, rule, view_func, **options): + self.url_map.add(Rule(rule, endpoint=view_func)) + + def register_blueprint(self, blueprint, **options): + """ + Registers a blueprint for web sockets like for 'Flask' application. + Decorator :meth:`~flask.app.setupmethod` is not applied, because it + requires ``debug`` and ``_got_first_request`` attributes to be defined. + """ + first_registration = False + + if blueprint.name in self.blueprints: + assert self.blueprints[blueprint.name] is blueprint, ( + "A blueprint's name collision occurred between %r and " + '%r. Both share the same name "%s". Blueprints that ' + "are created on the fly need unique names." + % (blueprint, self.blueprints[blueprint.name], blueprint.name) + ) + else: + self.blueprints[blueprint.name] = blueprint + self._blueprint_order.append(blueprint) + first_registration = True + + blueprint.register(self, options, first_registration) + + +def process_socket_message(message: str): + return f"Recieved: {message}" diff --git a/labthings/server/sockets/eventlet.py b/labthings/server/sockets/eventlet.py new file mode 100644 index 00000000..749a833d --- /dev/null +++ b/labthings/server/sockets/eventlet.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- + +from werkzeug.exceptions import NotFound +from werkzeug.http import parse_cookie +from werkzeug.wrappers import Request +from flask import request +import logging + +from .base import BaseSockets, process_socket_message +from eventlet import websocket +import eventlet + + +class SocketMiddleware(object): + def __init__(self, wsgi_app, app, socket): + self.ws = socket + self.app = app + self.wsgi_app = wsgi_app + + def __call__(self, environ, start_response): + request = Request(environ) + adapter = self.ws.url_map.bind_to_environ(environ) + + logging.debug(environ) + + try: # Try matching to a Sockets route + handler, values = adapter.match() + cookie = None + if "HTTP_COOKIE" in environ: + cookie = parse_cookie(environ["HTTP_COOKIE"]) + + with self.app.app_context(): + with self.app.request_context(environ): + # add cookie to the request to have correct session handling + request.cookie = cookie + + websocket.WebSocketWSGI(handler)(environ, start_response, **values) + return [] + except (NotFound, KeyError) as e: + return self.wsgi_app(environ, start_response) + + +class Sockets(BaseSockets): + def init_app(self, app): + app.wsgi_app = SocketMiddleware(app.wsgi_app, app, self) + + +def socket_handler_loop(ws): + while True: + message = ws.wait() + if message is None: + break + response = process_socket_message(message) + ws.send(response) diff --git a/labthings/server/sockets/gevent.py b/labthings/server/sockets/gevent.py new file mode 100644 index 00000000..0d064e44 --- /dev/null +++ b/labthings/server/sockets/gevent.py @@ -0,0 +1,58 @@ +""" +Copyright (C) 2013 Kenneth Reitz + +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. +""" + +# -*- coding: utf-8 -*- + +from werkzeug.exceptions import NotFound +from werkzeug.http import parse_cookie +from flask import request +import time +import logging + +from .base import BaseSockets, process_socket_message + + +class SocketMiddleware(object): + def __init__(self, wsgi_app, app, socket): + self.ws = socket + self.app = app + self.wsgi_app = wsgi_app + + def __call__(self, environ, start_response): + adapter = self.ws.url_map.bind_to_environ(environ) + try: + handler, values = adapter.match() + environment = environ["wsgi.websocket"] + cookie = None + if "HTTP_COOKIE" in environ: + cookie = parse_cookie(environ["HTTP_COOKIE"]) + + with self.app.app_context(): + with self.app.request_context(environ): + # add cookie to the request to have correct session handling + request.cookie = cookie + + handler(environment, **values) + return [] + except (NotFound, KeyError): + return self.wsgi_app(environ, start_response) + + +class Sockets(BaseSockets): + def init_app(self, app): + app.wsgi_app = SocketMiddleware(app.wsgi_app, app, self) + + +def socket_handler_loop(ws): + while not ws.closed: + message = ws.receive() + response = process_socket_message(message) + ws.send(response) + time.sleep(0.1) diff --git a/labthings/server/wsgi/__init__.py b/labthings/server/wsgi/__init__.py new file mode 100644 index 00000000..cb5570ca --- /dev/null +++ b/labthings/server/wsgi/__init__.py @@ -0,0 +1 @@ +from .eventlet import Server diff --git a/labthings/server/wsgi/eventlet.py b/labthings/server/wsgi/eventlet.py new file mode 100644 index 00000000..59e6e67c --- /dev/null +++ b/labthings/server/wsgi/eventlet.py @@ -0,0 +1,43 @@ +import eventlet.wsgi +import eventlet +import logging +import sys +import os +import signal +from werkzeug.debug import DebuggedApplication + + +class Server: + def __init__(self, app): + self.app = app + + def run(self, host="0.0.0.0", port=5000, log=None, debug=False, stop_timeout=1): + # Type checks + port = int(port) + host = str(host) + + # Unmodified version of app + app_to_run = self.app + + # Handle logging + if not log: + log = logging.getLogger() + + # Handle debug mode + # if debug: + # log.setLevel(logging.DEBUG) + # app_to_run = DebuggedApplication(self.app) + + friendlyhost = "localhost" if host == "0.0.0.0" else host + logging.info("Starting LabThings WSGI Server") + logging.info(f"Debug mode: {debug}") + logging.info(f"Running on http://{friendlyhost}:{port} (Press CTRL+C to quit)") + + # Create WSGIServer + addresses = eventlet.green.socket.getaddrinfo(host, port) + eventlet_socket = eventlet.listen(addresses[0][4], addresses[0][0]) + + try: + eventlet.wsgi.server(eventlet_socket, app_to_run) + except (KeyboardInterrupt, SystemExit): + logging.warning("Terminating by KeyboardInterrupt or SystemExit") diff --git a/labthings/server/wsgi.py b/labthings/server/wsgi/gevent.py similarity index 100% rename from labthings/server/wsgi.py rename to labthings/server/wsgi/gevent.py From bbcef31227bd9e3a40a3b53960d4c11422dd0480 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Thu, 12 Mar 2020 15:12:15 +0000 Subject: [PATCH 06/16] Updated to default install eventlet --- poetry.lock | 167 +++++++++++++++---------------------------------- pyproject.toml | 3 +- 2 files changed, 53 insertions(+), 117 deletions(-) diff --git a/poetry.lock b/poetry.lock index 87263a84..5d2a3bd7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -65,18 +65,6 @@ typed-ast = ">=1.4.0" [package.extras] d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] -[[package]] -category = "main" -description = "Foreign Function Interface for Python calling C code." -marker = "sys_platform == \"win32\" and platform_python_implementation == \"CPython\"" -name = "cffi" -optional = false -python-versions = "*" -version = "1.14.0" - -[package.dependencies] -pycparser = "*" - [[package]] category = "main" description = "Composable command line interface toolkit" @@ -94,6 +82,32 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" version = "0.4.3" +[[package]] +category = "main" +description = "DNS toolkit" +name = "dnspython" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.16.0" + +[package.extras] +DNSSEC = ["pycryptodome", "ecdsa (>=0.13)"] +IDNA = ["idna (>=2.1)"] + +[[package]] +category = "main" +description = "Highly concurrent networking library" +name = "eventlet" +optional = false +python-versions = "*" +version = "0.25.1" + +[package.dependencies] +dnspython = ">=1.15.0" +greenlet = ">=0.3" +monotonic = ">=1.4" +six = ">=1.10.0" + [[package]] category = "main" description = "A simple framework for building complex web applications." @@ -125,39 +139,9 @@ version = "3.0.8" Flask = ">=0.9" Six = "*" -[[package]] -category = "main" -description = "Coroutine-based network library" -name = "gevent" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" -version = "1.4.0" - -[package.dependencies] -cffi = ">=1.11.5" -greenlet = ">=0.4.14" - -[package.extras] -dnspython = ["dnspython", "idna"] -doc = ["repoze.sphinx.autointerface"] -events = ["zope.event", "zope.interface"] -test = ["zope.interface", "zope.event", "requests", "objgraph", "psutil", "futures", "mock", "coverage (>=5.0a3)", "coveralls (>=1.0)"] - -[[package]] -category = "main" -description = "Websocket handler for the gevent pywsgi server, a Python network library" -name = "gevent-websocket" -optional = false -python-versions = "*" -version = "0.10.1" - -[package.dependencies] -gevent = "*" - [[package]] category = "main" description = "Lightweight in-process concurrent programming" -marker = "platform_python_implementation == \"CPython\"" name = "greenlet" optional = false python-versions = "*" @@ -223,6 +207,14 @@ docs = ["sphinx (2.4.3)", "sphinx-issues (1.2.0)", "alabaster (0.7.12)", "sphinx lint = ["mypy (0.761)", "flake8 (3.7.9)", "flake8-bugbear (20.1.4)", "pre-commit (>=1.20,<3.0)"] tests = ["pytest", "pytz", "simplejson"] +[[package]] +category = "main" +description = "An implementation of time.monotonic() for Python 2 & < 3.3" +name = "monotonic" +optional = false +python-versions = "*" +version = "1.5" + [[package]] category = "dev" description = "More routines for operating on iterables, beyond itertools" @@ -275,15 +267,6 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "1.8.1" -[[package]] -category = "main" -description = "C parser in Python" -marker = "sys_platform == \"win32\" and platform_python_implementation == \"CPython\"" -name = "pycparser" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.20" - [[package]] category = "dev" description = "Python parsing module" @@ -411,7 +394,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["jaraco.itertools", "func-timeout"] [metadata] -content-hash = "450723557fea770ed1fb70140bda3d6de4b3d44896d92ac1f910c2154d492fcf" +content-hash = "414e68698e11e3bf6f3bccd663c404bfd71d52b35accfa8a60b02604f5dc90ec" python-versions = "^3.6" [metadata.files] @@ -435,36 +418,6 @@ black = [ {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, ] -cffi = [ - {file = "cffi-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384"}, - {file = "cffi-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30"}, - {file = "cffi-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"}, - {file = "cffi-1.14.0-cp27-cp27m-win32.whl", hash = "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78"}, - {file = "cffi-1.14.0-cp27-cp27m-win_amd64.whl", hash = "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793"}, - {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e"}, - {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a"}, - {file = "cffi-1.14.0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff"}, - {file = "cffi-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f"}, - {file = "cffi-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa"}, - {file = "cffi-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5"}, - {file = "cffi-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4"}, - {file = "cffi-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d"}, - {file = "cffi-1.14.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc"}, - {file = "cffi-1.14.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac"}, - {file = "cffi-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f"}, - {file = "cffi-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b"}, - {file = "cffi-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3"}, - {file = "cffi-1.14.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66"}, - {file = "cffi-1.14.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0"}, - {file = "cffi-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f"}, - {file = "cffi-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26"}, - {file = "cffi-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd"}, - {file = "cffi-1.14.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55"}, - {file = "cffi-1.14.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2"}, - {file = "cffi-1.14.0-cp38-cp38-win32.whl", hash = "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8"}, - {file = "cffi-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b"}, - {file = "cffi-1.14.0.tar.gz", hash = "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6"}, -] click = [ {file = "click-7.1.1-py2.py3-none-any.whl", hash = "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a"}, {file = "click-7.1.1.tar.gz", hash = "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc"}, @@ -473,6 +426,14 @@ colorama = [ {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, ] +dnspython = [ + {file = "dnspython-1.16.0-py2.py3-none-any.whl", hash = "sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d"}, + {file = "dnspython-1.16.0.zip", hash = "sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01"}, +] +eventlet = [ + {file = "eventlet-0.25.1-py2.py3-none-any.whl", hash = "sha256:658b1cd80937adc1a4860de2841e0528f64e2ca672885c4e00fc0e2217bde6b1"}, + {file = "eventlet-0.25.1.tar.gz", hash = "sha256:6c9c625af48424c4680d89314dbe45a76cc990cf002489f9469ff214b044ffc1"}, +] flask = [ {file = "Flask-1.1.1-py2.py3-none-any.whl", hash = "sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"}, {file = "Flask-1.1.1.tar.gz", hash = "sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52"}, @@ -481,35 +442,6 @@ flask-cors = [ {file = "Flask-Cors-3.0.8.tar.gz", hash = "sha256:72170423eb4612f0847318afff8c247b38bd516b7737adfc10d1c2cdbb382d16"}, {file = "Flask_Cors-3.0.8-py2.py3-none-any.whl", hash = "sha256:f4d97201660e6bbcff2d89d082b5b6d31abee04b1b3003ee073a6fd25ad1d69a"}, ] -gevent = [ - {file = "gevent-1.4.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b7d3a285978b27b469c0ff5fb5a72bcd69f4306dbbf22d7997d83209a8ba917"}, - {file = "gevent-1.4.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:44089ed06a962a3a70e96353c981d628b2d4a2f2a75ea5d90f916a62d22af2e8"}, - {file = "gevent-1.4.0-cp27-cp27m-win32.whl", hash = "sha256:0e1e5b73a445fe82d40907322e1e0eec6a6745ca3cea19291c6f9f50117bb7ea"}, - {file = "gevent-1.4.0-cp27-cp27m-win_amd64.whl", hash = "sha256:74b7528f901f39c39cdbb50cdf08f1a2351725d9aebaef212a29abfbb06895ee"}, - {file = "gevent-1.4.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0ff2b70e8e338cf13bedf146b8c29d475e2a544b5d1fe14045aee827c073842c"}, - {file = "gevent-1.4.0-cp34-cp34m-macosx_10_14_x86_64.whl", hash = "sha256:0774babec518a24d9a7231d4e689931f31b332c4517a771e532002614e270a64"}, - {file = "gevent-1.4.0-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:d752bcf1b98174780e2317ada12013d612f05116456133a6acf3e17d43b71f05"}, - {file = "gevent-1.4.0-cp34-cp34m-win32.whl", hash = "sha256:3249011d13d0c63bea72d91cec23a9cf18c25f91d1f115121e5c9113d753fa12"}, - {file = "gevent-1.4.0-cp34-cp34m-win_amd64.whl", hash = "sha256:d1e6d1f156e999edab069d79d890859806b555ce4e4da5b6418616322f0a3df1"}, - {file = "gevent-1.4.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7d0809e2991c9784eceeadef01c27ee6a33ca09ebba6154317a257353e3af922"}, - {file = "gevent-1.4.0-cp35-cp35m-win32.whl", hash = "sha256:14b4d06d19d39a440e72253f77067d27209c67e7611e352f79fe69e0f618f76e"}, - {file = "gevent-1.4.0-cp35-cp35m-win_amd64.whl", hash = "sha256:53b72385857e04e7faca13c613c07cab411480822ac658d97fd8a4ddbaf715c8"}, - {file = "gevent-1.4.0-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:8d9ec51cc06580f8c21b41fd3f2b3465197ba5b23c00eb7d422b7ae0380510b0"}, - {file = "gevent-1.4.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2711e69788ddb34c059a30186e05c55a6b611cb9e34ac343e69cf3264d42fe1c"}, - {file = "gevent-1.4.0-cp36-cp36m-win32.whl", hash = "sha256:e5bcc4270671936349249d26140c267397b7b4b1381f5ec8b13c53c5b53ab6e1"}, - {file = "gevent-1.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:9f7a1e96fec45f70ad364e46de32ccacab4d80de238bd3c2edd036867ccd48ad"}, - {file = "gevent-1.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:50024a1ee2cf04645535c5ebaeaa0a60c5ef32e262da981f4be0546b26791950"}, - {file = "gevent-1.4.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4bfa291e3c931ff3c99a349d8857605dca029de61d74c6bb82bd46373959c942"}, - {file = "gevent-1.4.0-cp37-cp37m-win32.whl", hash = "sha256:ab4dc33ef0e26dc627559786a4fba0c2227f125db85d970abbf85b77506b3f51"}, - {file = "gevent-1.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:896b2b80931d6b13b5d9feba3d4eebc67d5e6ec54f0cf3339d08487d55d93b0e"}, - {file = "gevent-1.4.0-pp260-pypy_41-macosx_10_14_x86_64.whl", hash = "sha256:107f4232db2172f7e8429ed7779c10f2ed16616d75ffbe77e0e0c3fcdeb51a51"}, - {file = "gevent-1.4.0-pp260-pypy_41-win32.whl", hash = "sha256:28a0c5417b464562ab9842dd1fb0cc1524e60494641d973206ec24d6ec5f6909"}, - {file = "gevent-1.4.0.tar.gz", hash = "sha256:1eb7fa3b9bd9174dfe9c3b59b7a09b768ecd496debfc4976a9530a3e15c990d1"}, -] -gevent-websocket = [ - {file = "gevent-websocket-0.10.1.tar.gz", hash = "sha256:7eaef32968290c9121f7c35b973e2cc302ffb076d018c9068d2f5ca8b2d85fb0"}, - {file = "gevent_websocket-0.10.1-py3-none-any.whl", hash = "sha256:17b67d91282f8f4c973eba0551183fc84f56f1c90c8f6b6b30256f31f66f5242"}, -] greenlet = [ {file = "greenlet-0.4.15-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99a26afdb82ea83a265137a398f570402aa1f2b5dfb4ac3300c026931817b163"}, {file = "greenlet-0.4.15-cp27-cp27m-win32.whl", hash = "sha256:beeabe25c3b704f7d56b573f7d2ff88fc99f0138e43480cecdfcaa3b87fe4f87"}, @@ -574,12 +506,21 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] marshmallow = [ {file = "marshmallow-3.5.1-py2.py3-none-any.whl", hash = "sha256:ac2e13b30165501b7d41fc0371b8df35944f5849769d136f20e2c5f6cdc6e665"}, {file = "marshmallow-3.5.1.tar.gz", hash = "sha256:90854221bbb1498d003a0c3cc9d8390259137551917961c8b5258c64026b2f85"}, ] +monotonic = [ + {file = "monotonic-1.5-py2.py3-none-any.whl", hash = "sha256:552a91f381532e33cbd07c6a2655a21908088962bb8fa7239ecbcc6ad1140cc7"}, + {file = "monotonic-1.5.tar.gz", hash = "sha256:23953d55076df038541e648a53676fb24980f7a1be290cdda21300b3bc21dfb0"}, +] more-itertools = [ {file = "more-itertools-8.2.0.tar.gz", hash = "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"}, {file = "more_itertools-8.2.0-py3-none-any.whl", hash = "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c"}, @@ -600,10 +541,6 @@ py = [ {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"}, ] -pycparser = [ - {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, - {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, -] pyparsing = [ {file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"}, {file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"}, diff --git a/pyproject.toml b/pyproject.toml index fc82c801..03666e2c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,8 +11,7 @@ marshmallow = "^3.4.0" webargs = "^5.5.3" apispec = "^3.2.0" flask-cors = "^3.0.8" -gevent = "^1.4.0" -gevent-websocket = "^0.10.1" +eventlet = "^0.25.1" [tool.poetry.dev-dependencies] pytest = "^5.2" From 62fcdbb33f6c1a3e5a8c1ca3fd983d64bf9bcef0 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Thu, 12 Mar 2020 16:03:05 +0000 Subject: [PATCH 07/16] Moved monkey patches to server submodules --- labthings/server/__init__.py | 4 ---- labthings/server/wsgi/eventlet.py | 3 +++ labthings/server/wsgi/gevent.py | 5 +++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/labthings/server/__init__.py b/labthings/server/__init__.py index 44dc16f5..1d3f44bd 100644 --- a/labthings/server/__init__.py +++ b/labthings/server/__init__.py @@ -1,5 +1 @@ EXTENSION_NAME = "flask-labthings" - -from gevent import monkey - -monkey.patch_all() diff --git a/labthings/server/wsgi/eventlet.py b/labthings/server/wsgi/eventlet.py index 59e6e67c..318415db 100644 --- a/labthings/server/wsgi/eventlet.py +++ b/labthings/server/wsgi/eventlet.py @@ -6,6 +6,9 @@ import signal from werkzeug.debug import DebuggedApplication +# Monkey patching is bad and should never be done +eventlet.monkey_patch() + class Server: def __init__(self, app): diff --git a/labthings/server/wsgi/gevent.py b/labthings/server/wsgi/gevent.py index fbe6f20a..1125a3f2 100644 --- a/labthings/server/wsgi/gevent.py +++ b/labthings/server/wsgi/gevent.py @@ -6,6 +6,11 @@ import signal from werkzeug.debug import DebuggedApplication +from gevent import monkey + +# Monkey patching is bad and should never be done +monkey.patch_all() + class Server: def __init__(self, app): From 8389b916c17e1474dd38b29183cfc964c5ba9b69 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Thu, 12 Mar 2020 17:15:37 +0000 Subject: [PATCH 08/16] Only let eventlet websockets handle connections requesting an upgrade --- labthings/server/sockets/eventlet.py | 38 ++++++++++++++++------------ 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/labthings/server/sockets/eventlet.py b/labthings/server/sockets/eventlet.py index 749a833d..4d0d1cad 100644 --- a/labthings/server/sockets/eventlet.py +++ b/labthings/server/sockets/eventlet.py @@ -4,6 +4,7 @@ from werkzeug.http import parse_cookie from werkzeug.wrappers import Request from flask import request +from pprint import pformat import logging from .base import BaseSockets, process_socket_message @@ -21,22 +22,27 @@ def __call__(self, environ, start_response): request = Request(environ) adapter = self.ws.url_map.bind_to_environ(environ) - logging.debug(environ) - - try: # Try matching to a Sockets route - handler, values = adapter.match() - cookie = None - if "HTTP_COOKIE" in environ: - cookie = parse_cookie(environ["HTTP_COOKIE"]) - - with self.app.app_context(): - with self.app.request_context(environ): - # add cookie to the request to have correct session handling - request.cookie = cookie - - websocket.WebSocketWSGI(handler)(environ, start_response, **values) - return [] - except (NotFound, KeyError) as e: + logging.debug(pformat(environ)) + + if environ.get("HTTP_UPGRADE") == "websocket": + try: # Try matching to a Sockets route + handler, values = adapter.match() + cookie = None + if "HTTP_COOKIE" in environ: + cookie = parse_cookie(environ["HTTP_COOKIE"]) + + with self.app.app_context(): + with self.app.request_context(environ): + # add cookie to the request to have correct session handling + request.cookie = cookie + + websocket.WebSocketWSGI(handler)( + environ, start_response, **values + ) + return [] + except (NotFound, KeyError): # If no socket route found, fall back to WSGI + return self.wsgi_app(environ, start_response) + else: # If not upgrading to a websocket return self.wsgi_app(environ, start_response) From 2b08c26f9187dc8287bc67396173141b8c849892 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Thu, 12 Mar 2020 17:21:24 +0000 Subject: [PATCH 09/16] Fixed websocket route URL --- labthings/server/labthing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/labthings/server/labthing.py b/labthings/server/labthing.py index e9179539..97990ee2 100644 --- a/labthings/server/labthing.py +++ b/labthings/server/labthing.py @@ -134,7 +134,7 @@ def _create_base_routes(self): self.add_view(TaskView, "/tasks/", endpoint=TASK_ENDPOINT) def _create_base_sockets(self): - self.sockets.add_url_rule("/", self._socket_handler) + self.sockets.add_url_rule(f"{self.url_prefix}", self._socket_handler) def _socket_handler(self, ws): # Create a socket subscriber From c7cfc941596ffe92caa1a295b512098dd459f2b1 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Thu, 12 Mar 2020 17:27:41 +0000 Subject: [PATCH 10/16] Added eventlet server debug and log options --- labthings/server/wsgi/eventlet.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/labthings/server/wsgi/eventlet.py b/labthings/server/wsgi/eventlet.py index 318415db..dfa3b747 100644 --- a/labthings/server/wsgi/eventlet.py +++ b/labthings/server/wsgi/eventlet.py @@ -26,11 +26,6 @@ def run(self, host="0.0.0.0", port=5000, log=None, debug=False, stop_timeout=1): if not log: log = logging.getLogger() - # Handle debug mode - # if debug: - # log.setLevel(logging.DEBUG) - # app_to_run = DebuggedApplication(self.app) - friendlyhost = "localhost" if host == "0.0.0.0" else host logging.info("Starting LabThings WSGI Server") logging.info(f"Debug mode: {debug}") @@ -41,6 +36,6 @@ def run(self, host="0.0.0.0", port=5000, log=None, debug=False, stop_timeout=1): eventlet_socket = eventlet.listen(addresses[0][4], addresses[0][0]) try: - eventlet.wsgi.server(eventlet_socket, app_to_run) + eventlet.wsgi.server(eventlet_socket, app_to_run, log=log, debug=debug) except (KeyboardInterrupt, SystemExit): logging.warning("Terminating by KeyboardInterrupt or SystemExit") From 000e833a1bdea1f0a866856eaea99c6641a5314c Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Thu, 12 Mar 2020 17:50:26 +0000 Subject: [PATCH 11/16] Reverted to gevent default (OFM live stream breaks with eventlet) --- labthings/server/sockets/__init__.py | 2 +- labthings/server/wsgi/__init__.py | 2 +- poetry.lock | 115 ++++++++++++++++++++++++++- pyproject.toml | 2 + 4 files changed, 118 insertions(+), 3 deletions(-) diff --git a/labthings/server/sockets/__init__.py b/labthings/server/sockets/__init__.py index 715d714a..b4771db2 100644 --- a/labthings/server/sockets/__init__.py +++ b/labthings/server/sockets/__init__.py @@ -1,2 +1,2 @@ from .base import SocketSubscriber -from .eventlet import Sockets, socket_handler_loop +from .gevent import Sockets, socket_handler_loop diff --git a/labthings/server/wsgi/__init__.py b/labthings/server/wsgi/__init__.py index cb5570ca..8be7071e 100644 --- a/labthings/server/wsgi/__init__.py +++ b/labthings/server/wsgi/__init__.py @@ -1 +1 @@ -from .eventlet import Server +from .gevent import Server diff --git a/poetry.lock b/poetry.lock index 5d2a3bd7..b1d96c3c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -65,6 +65,18 @@ typed-ast = ">=1.4.0" [package.extras] d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] +[[package]] +category = "main" +description = "Foreign Function Interface for Python calling C code." +marker = "sys_platform == \"win32\" and platform_python_implementation == \"CPython\"" +name = "cffi" +optional = false +python-versions = "*" +version = "1.14.0" + +[package.dependencies] +pycparser = "*" + [[package]] category = "main" description = "Composable command line interface toolkit" @@ -139,6 +151,35 @@ version = "3.0.8" Flask = ">=0.9" Six = "*" +[[package]] +category = "main" +description = "Coroutine-based network library" +name = "gevent" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +version = "1.4.0" + +[package.dependencies] +cffi = ">=1.11.5" +greenlet = ">=0.4.14" + +[package.extras] +dnspython = ["dnspython", "idna"] +doc = ["repoze.sphinx.autointerface"] +events = ["zope.event", "zope.interface"] +test = ["zope.interface", "zope.event", "requests", "objgraph", "psutil", "futures", "mock", "coverage (>=5.0a3)", "coveralls (>=1.0)"] + +[[package]] +category = "main" +description = "Websocket handler for the gevent pywsgi server, a Python network library" +name = "gevent-websocket" +optional = false +python-versions = "*" +version = "0.10.1" + +[package.dependencies] +gevent = "*" + [[package]] category = "main" description = "Lightweight in-process concurrent programming" @@ -267,6 +308,15 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "1.8.1" +[[package]] +category = "main" +description = "C parser in Python" +marker = "sys_platform == \"win32\" and platform_python_implementation == \"CPython\"" +name = "pycparser" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.20" + [[package]] category = "dev" description = "Python parsing module" @@ -394,7 +444,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["jaraco.itertools", "func-timeout"] [metadata] -content-hash = "414e68698e11e3bf6f3bccd663c404bfd71d52b35accfa8a60b02604f5dc90ec" +content-hash = "7f7cbd2c19a64b252ed988c6e59daccb7f3bcbe6c8aa97fd97b29a97fc6aef75" python-versions = "^3.6" [metadata.files] @@ -418,6 +468,36 @@ black = [ {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, ] +cffi = [ + {file = "cffi-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384"}, + {file = "cffi-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30"}, + {file = "cffi-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"}, + {file = "cffi-1.14.0-cp27-cp27m-win32.whl", hash = "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78"}, + {file = "cffi-1.14.0-cp27-cp27m-win_amd64.whl", hash = "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793"}, + {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e"}, + {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a"}, + {file = "cffi-1.14.0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff"}, + {file = "cffi-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f"}, + {file = "cffi-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa"}, + {file = "cffi-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5"}, + {file = "cffi-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4"}, + {file = "cffi-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d"}, + {file = "cffi-1.14.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc"}, + {file = "cffi-1.14.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac"}, + {file = "cffi-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f"}, + {file = "cffi-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b"}, + {file = "cffi-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3"}, + {file = "cffi-1.14.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66"}, + {file = "cffi-1.14.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0"}, + {file = "cffi-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f"}, + {file = "cffi-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26"}, + {file = "cffi-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd"}, + {file = "cffi-1.14.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55"}, + {file = "cffi-1.14.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2"}, + {file = "cffi-1.14.0-cp38-cp38-win32.whl", hash = "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8"}, + {file = "cffi-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b"}, + {file = "cffi-1.14.0.tar.gz", hash = "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6"}, +] click = [ {file = "click-7.1.1-py2.py3-none-any.whl", hash = "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a"}, {file = "click-7.1.1.tar.gz", hash = "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc"}, @@ -442,6 +522,35 @@ flask-cors = [ {file = "Flask-Cors-3.0.8.tar.gz", hash = "sha256:72170423eb4612f0847318afff8c247b38bd516b7737adfc10d1c2cdbb382d16"}, {file = "Flask_Cors-3.0.8-py2.py3-none-any.whl", hash = "sha256:f4d97201660e6bbcff2d89d082b5b6d31abee04b1b3003ee073a6fd25ad1d69a"}, ] +gevent = [ + {file = "gevent-1.4.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b7d3a285978b27b469c0ff5fb5a72bcd69f4306dbbf22d7997d83209a8ba917"}, + {file = "gevent-1.4.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:44089ed06a962a3a70e96353c981d628b2d4a2f2a75ea5d90f916a62d22af2e8"}, + {file = "gevent-1.4.0-cp27-cp27m-win32.whl", hash = "sha256:0e1e5b73a445fe82d40907322e1e0eec6a6745ca3cea19291c6f9f50117bb7ea"}, + {file = "gevent-1.4.0-cp27-cp27m-win_amd64.whl", hash = "sha256:74b7528f901f39c39cdbb50cdf08f1a2351725d9aebaef212a29abfbb06895ee"}, + {file = "gevent-1.4.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0ff2b70e8e338cf13bedf146b8c29d475e2a544b5d1fe14045aee827c073842c"}, + {file = "gevent-1.4.0-cp34-cp34m-macosx_10_14_x86_64.whl", hash = "sha256:0774babec518a24d9a7231d4e689931f31b332c4517a771e532002614e270a64"}, + {file = "gevent-1.4.0-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:d752bcf1b98174780e2317ada12013d612f05116456133a6acf3e17d43b71f05"}, + {file = "gevent-1.4.0-cp34-cp34m-win32.whl", hash = "sha256:3249011d13d0c63bea72d91cec23a9cf18c25f91d1f115121e5c9113d753fa12"}, + {file = "gevent-1.4.0-cp34-cp34m-win_amd64.whl", hash = "sha256:d1e6d1f156e999edab069d79d890859806b555ce4e4da5b6418616322f0a3df1"}, + {file = "gevent-1.4.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7d0809e2991c9784eceeadef01c27ee6a33ca09ebba6154317a257353e3af922"}, + {file = "gevent-1.4.0-cp35-cp35m-win32.whl", hash = "sha256:14b4d06d19d39a440e72253f77067d27209c67e7611e352f79fe69e0f618f76e"}, + {file = "gevent-1.4.0-cp35-cp35m-win_amd64.whl", hash = "sha256:53b72385857e04e7faca13c613c07cab411480822ac658d97fd8a4ddbaf715c8"}, + {file = "gevent-1.4.0-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:8d9ec51cc06580f8c21b41fd3f2b3465197ba5b23c00eb7d422b7ae0380510b0"}, + {file = "gevent-1.4.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2711e69788ddb34c059a30186e05c55a6b611cb9e34ac343e69cf3264d42fe1c"}, + {file = "gevent-1.4.0-cp36-cp36m-win32.whl", hash = "sha256:e5bcc4270671936349249d26140c267397b7b4b1381f5ec8b13c53c5b53ab6e1"}, + {file = "gevent-1.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:9f7a1e96fec45f70ad364e46de32ccacab4d80de238bd3c2edd036867ccd48ad"}, + {file = "gevent-1.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:50024a1ee2cf04645535c5ebaeaa0a60c5ef32e262da981f4be0546b26791950"}, + {file = "gevent-1.4.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4bfa291e3c931ff3c99a349d8857605dca029de61d74c6bb82bd46373959c942"}, + {file = "gevent-1.4.0-cp37-cp37m-win32.whl", hash = "sha256:ab4dc33ef0e26dc627559786a4fba0c2227f125db85d970abbf85b77506b3f51"}, + {file = "gevent-1.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:896b2b80931d6b13b5d9feba3d4eebc67d5e6ec54f0cf3339d08487d55d93b0e"}, + {file = "gevent-1.4.0-pp260-pypy_41-macosx_10_14_x86_64.whl", hash = "sha256:107f4232db2172f7e8429ed7779c10f2ed16616d75ffbe77e0e0c3fcdeb51a51"}, + {file = "gevent-1.4.0-pp260-pypy_41-win32.whl", hash = "sha256:28a0c5417b464562ab9842dd1fb0cc1524e60494641d973206ec24d6ec5f6909"}, + {file = "gevent-1.4.0.tar.gz", hash = "sha256:1eb7fa3b9bd9174dfe9c3b59b7a09b768ecd496debfc4976a9530a3e15c990d1"}, +] +gevent-websocket = [ + {file = "gevent-websocket-0.10.1.tar.gz", hash = "sha256:7eaef32968290c9121f7c35b973e2cc302ffb076d018c9068d2f5ca8b2d85fb0"}, + {file = "gevent_websocket-0.10.1-py3-none-any.whl", hash = "sha256:17b67d91282f8f4c973eba0551183fc84f56f1c90c8f6b6b30256f31f66f5242"}, +] greenlet = [ {file = "greenlet-0.4.15-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99a26afdb82ea83a265137a398f570402aa1f2b5dfb4ac3300c026931817b163"}, {file = "greenlet-0.4.15-cp27-cp27m-win32.whl", hash = "sha256:beeabe25c3b704f7d56b573f7d2ff88fc99f0138e43480cecdfcaa3b87fe4f87"}, @@ -541,6 +650,10 @@ py = [ {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"}, ] +pycparser = [ + {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, + {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, +] pyparsing = [ {file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"}, {file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"}, diff --git a/pyproject.toml b/pyproject.toml index 03666e2c..7e3b8498 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,8 @@ webargs = "^5.5.3" apispec = "^3.2.0" flask-cors = "^3.0.8" eventlet = "^0.25.1" +gevent = "^1.4.0" +gevent-websocket = "^0.10.1" [tool.poetry.dev-dependencies] pytest = "^5.2" From 535afebc70dd64551ca910daa766a0ed9ba1b3fc Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Thu, 12 Mar 2020 18:47:54 +0000 Subject: [PATCH 12/16] Moved monkey patch back to server top level --- labthings/server/__init__.py | 10 ++++++++++ labthings/server/sockets/gevent.py | 3 ++- labthings/server/wsgi/eventlet.py | 3 --- labthings/server/wsgi/gevent.py | 5 ----- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/labthings/server/__init__.py b/labthings/server/__init__.py index 1d3f44bd..9095609f 100644 --- a/labthings/server/__init__.py +++ b/labthings/server/__init__.py @@ -1 +1,11 @@ +import logging + EXTENSION_NAME = "flask-labthings" + +# Monkey patching is bad and should never be done +# import eventlet +# eventlet.monkey_patch() + +from gevent import monkey + +monkey.patch_all() diff --git a/labthings/server/sockets/gevent.py b/labthings/server/sockets/gevent.py index 0d064e44..7c7005e5 100644 --- a/labthings/server/sockets/gevent.py +++ b/labthings/server/sockets/gevent.py @@ -13,6 +13,7 @@ from werkzeug.exceptions import NotFound from werkzeug.http import parse_cookie from flask import request +import gevent import time import logging @@ -55,4 +56,4 @@ def socket_handler_loop(ws): message = ws.receive() response = process_socket_message(message) ws.send(response) - time.sleep(0.1) + gevent.sleep(0.1) diff --git a/labthings/server/wsgi/eventlet.py b/labthings/server/wsgi/eventlet.py index dfa3b747..53df0f15 100644 --- a/labthings/server/wsgi/eventlet.py +++ b/labthings/server/wsgi/eventlet.py @@ -6,9 +6,6 @@ import signal from werkzeug.debug import DebuggedApplication -# Monkey patching is bad and should never be done -eventlet.monkey_patch() - class Server: def __init__(self, app): diff --git a/labthings/server/wsgi/gevent.py b/labthings/server/wsgi/gevent.py index 1125a3f2..fbe6f20a 100644 --- a/labthings/server/wsgi/gevent.py +++ b/labthings/server/wsgi/gevent.py @@ -6,11 +6,6 @@ import signal from werkzeug.debug import DebuggedApplication -from gevent import monkey - -# Monkey patching is bad and should never be done -monkey.patch_all() - class Server: def __init__(self, app): From 8e2383f91aed22dd9e8b9ffb45b9b03c868378a9 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Thu, 12 Mar 2020 19:07:23 +0000 Subject: [PATCH 13/16] Better handle requests without websocket upgrade --- labthings/server/sockets/gevent.py | 45 +++++++++++++++++------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/labthings/server/sockets/gevent.py b/labthings/server/sockets/gevent.py index 7c7005e5..eb0ed70c 100644 --- a/labthings/server/sockets/gevent.py +++ b/labthings/server/sockets/gevent.py @@ -1,10 +1,7 @@ """ -Copyright (C) 2013 Kenneth Reitz - +Once upon a time, based on flask-websocket; Copyright (C) 2013 Kenneth Reitz 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. """ @@ -28,21 +25,25 @@ def __init__(self, wsgi_app, app, socket): def __call__(self, environ, start_response): adapter = self.ws.url_map.bind_to_environ(environ) - try: - handler, values = adapter.match() - environment = environ["wsgi.websocket"] - cookie = None - if "HTTP_COOKIE" in environ: - cookie = parse_cookie(environ["HTTP_COOKIE"]) - - with self.app.app_context(): - with self.app.request_context(environ): - # add cookie to the request to have correct session handling - request.cookie = cookie - - handler(environment, **values) - return [] - except (NotFound, KeyError): + + if environ.get("HTTP_UPGRADE") == "websocket": + try: # Try matching to a Sockets route + handler, values = adapter.match() + environment = environ["wsgi.websocket"] + cookie = None + if "HTTP_COOKIE" in environ: + cookie = parse_cookie(environ["HTTP_COOKIE"]) + + with self.app.app_context(): + with self.app.request_context(environ): + # add cookie to the request to have correct session handling + request.cookie = cookie + + handler(environment, **values) + return [] + except (NotFound, KeyError): + return self.wsgi_app(environ, start_response) + else: # If not upgrading to a websocket return self.wsgi_app(environ, start_response) @@ -54,6 +55,10 @@ def init_app(self, app): def socket_handler_loop(ws): while not ws.closed: message = ws.receive() + if message is None: + break response = process_socket_message(message) - ws.send(response) + if response: + logging.info(response) + ws.send(response) gevent.sleep(0.1) From b54c6f5a11bb4ed79d1b97d94ba0e43205c5b3c2 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Thu, 12 Mar 2020 19:07:32 +0000 Subject: [PATCH 14/16] Better handle NULL websocket messages --- labthings/server/sockets/base.py | 5 ++++- labthings/server/sockets/eventlet.py | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/labthings/server/sockets/base.py b/labthings/server/sockets/base.py index 9c3fb369..aeb6b51a 100644 --- a/labthings/server/sockets/base.py +++ b/labthings/server/sockets/base.py @@ -86,4 +86,7 @@ def register_blueprint(self, blueprint, **options): def process_socket_message(message: str): - return f"Recieved: {message}" + if message: + return f"Recieved: {message}" + else: + return None diff --git a/labthings/server/sockets/eventlet.py b/labthings/server/sockets/eventlet.py index 4d0d1cad..178c07a7 100644 --- a/labthings/server/sockets/eventlet.py +++ b/labthings/server/sockets/eventlet.py @@ -57,4 +57,5 @@ def socket_handler_loop(ws): if message is None: break response = process_socket_message(message) - ws.send(response) + if response: + ws.send(response) From 073ecb5c4d1e04f16d48f9ccb3cb78d4aba4fe8c Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Fri, 13 Mar 2020 15:45:00 +0000 Subject: [PATCH 15/16] Removed default websocket echo --- labthings/server/sockets/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/labthings/server/sockets/base.py b/labthings/server/sockets/base.py index aeb6b51a..b3caaeb3 100644 --- a/labthings/server/sockets/base.py +++ b/labthings/server/sockets/base.py @@ -87,6 +87,7 @@ def register_blueprint(self, blueprint, **options): def process_socket_message(message: str): if message: - return f"Recieved: {message}" + # return f"Recieved: {message}" + return None else: return None From 8c09f84f41ff76fa6b261d782d9beeff5b8fc124 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Fri, 13 Mar 2020 15:45:09 +0000 Subject: [PATCH 16/16] Removed eventlet dependency --- poetry.lock | 49 ++----------------------------------------------- pyproject.toml | 1 - 2 files changed, 2 insertions(+), 48 deletions(-) diff --git a/poetry.lock b/poetry.lock index b1d96c3c..b7ce05f7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -94,32 +94,6 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" version = "0.4.3" -[[package]] -category = "main" -description = "DNS toolkit" -name = "dnspython" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.16.0" - -[package.extras] -DNSSEC = ["pycryptodome", "ecdsa (>=0.13)"] -IDNA = ["idna (>=2.1)"] - -[[package]] -category = "main" -description = "Highly concurrent networking library" -name = "eventlet" -optional = false -python-versions = "*" -version = "0.25.1" - -[package.dependencies] -dnspython = ">=1.15.0" -greenlet = ">=0.3" -monotonic = ">=1.4" -six = ">=1.10.0" - [[package]] category = "main" description = "A simple framework for building complex web applications." @@ -183,6 +157,7 @@ gevent = "*" [[package]] category = "main" description = "Lightweight in-process concurrent programming" +marker = "platform_python_implementation == \"CPython\"" name = "greenlet" optional = false python-versions = "*" @@ -248,14 +223,6 @@ docs = ["sphinx (2.4.3)", "sphinx-issues (1.2.0)", "alabaster (0.7.12)", "sphinx lint = ["mypy (0.761)", "flake8 (3.7.9)", "flake8-bugbear (20.1.4)", "pre-commit (>=1.20,<3.0)"] tests = ["pytest", "pytz", "simplejson"] -[[package]] -category = "main" -description = "An implementation of time.monotonic() for Python 2 & < 3.3" -name = "monotonic" -optional = false -python-versions = "*" -version = "1.5" - [[package]] category = "dev" description = "More routines for operating on iterables, beyond itertools" @@ -444,7 +411,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["jaraco.itertools", "func-timeout"] [metadata] -content-hash = "7f7cbd2c19a64b252ed988c6e59daccb7f3bcbe6c8aa97fd97b29a97fc6aef75" +content-hash = "450723557fea770ed1fb70140bda3d6de4b3d44896d92ac1f910c2154d492fcf" python-versions = "^3.6" [metadata.files] @@ -506,14 +473,6 @@ colorama = [ {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, ] -dnspython = [ - {file = "dnspython-1.16.0-py2.py3-none-any.whl", hash = "sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d"}, - {file = "dnspython-1.16.0.zip", hash = "sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01"}, -] -eventlet = [ - {file = "eventlet-0.25.1-py2.py3-none-any.whl", hash = "sha256:658b1cd80937adc1a4860de2841e0528f64e2ca672885c4e00fc0e2217bde6b1"}, - {file = "eventlet-0.25.1.tar.gz", hash = "sha256:6c9c625af48424c4680d89314dbe45a76cc990cf002489f9469ff214b044ffc1"}, -] flask = [ {file = "Flask-1.1.1-py2.py3-none-any.whl", hash = "sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"}, {file = "Flask-1.1.1.tar.gz", hash = "sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52"}, @@ -626,10 +585,6 @@ marshmallow = [ {file = "marshmallow-3.5.1-py2.py3-none-any.whl", hash = "sha256:ac2e13b30165501b7d41fc0371b8df35944f5849769d136f20e2c5f6cdc6e665"}, {file = "marshmallow-3.5.1.tar.gz", hash = "sha256:90854221bbb1498d003a0c3cc9d8390259137551917961c8b5258c64026b2f85"}, ] -monotonic = [ - {file = "monotonic-1.5-py2.py3-none-any.whl", hash = "sha256:552a91f381532e33cbd07c6a2655a21908088962bb8fa7239ecbcc6ad1140cc7"}, - {file = "monotonic-1.5.tar.gz", hash = "sha256:23953d55076df038541e648a53676fb24980f7a1be290cdda21300b3bc21dfb0"}, -] more-itertools = [ {file = "more-itertools-8.2.0.tar.gz", hash = "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"}, {file = "more_itertools-8.2.0-py3-none-any.whl", hash = "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c"}, diff --git a/pyproject.toml b/pyproject.toml index 7e3b8498..fc82c801 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,6 @@ marshmallow = "^3.4.0" webargs = "^5.5.3" apispec = "^3.2.0" flask-cors = "^3.0.8" -eventlet = "^0.25.1" gevent = "^1.4.0" gevent-websocket = "^0.10.1"