Explorar o código

[Python] Falcon: unifying WSGI application (#8111)

Oleg S %!s(int64=2) %!d(string=hai) anos
pai
achega
886df89d6d

+ 0 - 49
frameworks/Python/falcon/app-socketify-wsgi.py

@@ -1,49 +0,0 @@
-#!/usr/bin/env python
-import falcon
-from socketify import WSGI
-import os
-import multiprocessing
-import logging
-
-# setup
-wsgi = app = falcon.App()
-
-# resource endpoints
-class JSONResource(object):
-    def on_get(self, request, response):
-        response.media = {'message': "Hello, world!"}
-
-class PlaintextResource(object):
-    def on_get(self, request, response):
-        response.content_type = falcon.MEDIA_TEXT
-        response.text = 'Hello, world!'
-
-
-# register resources
-app.add_route("/json", JSONResource())
-app.add_route("/plaintext", PlaintextResource())
-
-
-_is_travis = os.environ.get('TRAVIS') == 'true'
-
-workers = int(multiprocessing.cpu_count())
-if _is_travis:
-    workers = 2
-
-
-def run_app():
-    WSGI(app).listen(8080, lambda config: logging.info(f"Listening on port http://localhost:{config.port} now\n")).run()
-
-
-def create_fork():
-    n = os.fork()
-    # n greater than 0 means parent process
-    if not n > 0:
-        run_app()
-
-
-# fork limiting the cpu count - 1
-for i in range(1, multiprocessing.cpu_count()):
-    create_fork()
-
-run_app()  # run app on the main process too :)

+ 118 - 0
frameworks/Python/falcon/app.py

@@ -1,18 +1,47 @@
 #!/usr/bin/env python
+import os
+import sys
 import falcon
+
 from db_orm import session, World, Fortune
 from helpers import load_template, FortuneTuple, generate_ids, sanitize
 from operator import attrgetter
 from random import randint
+from email.utils import formatdate
 
 
+# ------------------------------------------------------------------
 # setup
 wsgi = app = falcon.App()
 
+response_server = "Falcon"
+response_add_date = False
+
+def add_ext_headers(response):
+    if response_server:
+        response.set_header('Server', response_server)
+
+    if response_add_date:
+        response.set_header('Date', formatdate(timeval=None, localtime=False, usegmt=True))
+
 
+if os.getenv('USE_ORJSON', "0") == "1":
+    import orjson
+    # custom JSON handler
+    JSONHandler = falcon.media.JSONHandler(dumps=orjson.dumps, loads=orjson.loads)
+    extra_handlers = {
+        "application/json": JSONHandler,
+        "application/json; charset=UTF-8": JSONHandler
+    }
+    wsgi.req_options.media_handlers.update(extra_handlers)
+    wsgi.resp_options.media_handlers.update(extra_handlers) 
+
+# ------------------------------------------------------------------
 # resource endpoints
+
 class JSONResource(object):
     def on_get(self, request, response):
+        add_ext_headers(response)
         response.media = {'message': "Hello, world!"}
 
 
@@ -21,6 +50,7 @@ class SingleQuery(object):
     def on_get(self, request, response):
         wid = randint(1, 10000)
         world = World[wid]
+        add_ext_headers(response)
         response.media = world.to_dict()
 
 
@@ -29,6 +59,7 @@ class MultipleQueries(object):
     def on_get(self, request, response, num):
         num = sanitize(num)
         worlds = [World[ident].to_dict() for ident in generate_ids(num)]
+        add_ext_headers(response)
         response.media = worlds
 
 
@@ -43,6 +74,7 @@ class UpdateQueries(object):
             world = World[item]
             world.randomNumber = randint(1, 10000)
             worlds.append({"id": world.id, "randomNumber": world.randomNumber})
+        add_ext_headers(response)
         response.media = worlds
 
 
@@ -55,12 +87,14 @@ class Fortunes(object):
         fortunes.append(FortuneTuple(id=0, message="Additional fortune added at request time."))
         fortunes.sort(key=attrgetter("message"))
         content = self._template.render(fortunes=fortunes)
+        add_ext_headers(response)
         response.content_type = falcon.MEDIA_HTML
         response.text = content
 
 
 class PlaintextResource(object):
     def on_get(self, request, response):
+        add_ext_headers(response)
         response.content_type = falcon.MEDIA_TEXT
         response.text = 'Hello, world!'
 
@@ -72,3 +106,87 @@ app.add_route("/queries/{num}", MultipleQueries())
 app.add_route("/updates/{num}", UpdateQueries())
 app.add_route("/fortunes", Fortunes())
 app.add_route("/plaintext", PlaintextResource())
+
+# ------------------------------------------------------------------
+
+if __name__ == "__main__":
+    import optparse
+    import multiprocessing
+    import logging
+    import re
+
+    parser = optparse.OptionParser("usage: %prog [options]", add_help_option=False)
+    parser.add_option("-h", "--host", dest="host", default='0.0.0.0', type="string")
+    parser.add_option("-p", "--port", dest="port", default=8080, type="int")
+    parser.add_option("-s", "--server", dest="server", default="gunicorn", type="string")
+    parser.add_option("-w", "--workers", dest="workers", default=0, type="int")
+    parser.add_option("-k", "--keepalive", dest="keepalive", default=60, type="int")
+    parser.add_option("-v", "--verbose", dest="verbose", default=0, type="int")
+    (opt, args) = parser.parse_args() 
+
+    _is_travis = os.environ.get('TRAVIS') == 'true'
+
+    workers = opt.workers
+    if workers <= 0:
+        workers = int(multiprocessing.cpu_count())
+
+    if _is_travis:
+        workers = 2
+
+    if opt.server == 'waitress':
+        import waitress
+        response_server = None
+        response_add_date = False
+        logging.basicConfig()
+        logging.getLogger().setLevel(logging.CRITICAL)
+        logging.disable(True)
+        waitress.serve(
+            app=wsgi,
+            listen=f"{opt.host}:{opt.port}",
+            log_socket_errors=False,
+            threads=workers,
+            asyncore_use_poll=True,
+            expose_tracebacks=False,
+            connection_limit=128,
+            channel_timeout=opt.keepalive,
+            _quiet=True) 
+        sys.exit(0)
+
+    def run_app():
+        global response_server
+        global response_add_date
+        
+        if opt.server == 'bjoern':
+            import bjoern
+            # Note:
+            # Bjoern doesn't provide any additional response headers like Date and Server
+            # so we need to provide them manually.
+            bjoern_version = [i for i in open('requirements-bjoern.txt', 'r') if re.search('bjoern', i)][0].strip().split('==')
+            response_server = '{}/{}'.format(*bjoern_version).title()
+            response_add_date = True
+            bjoern.run(app, host=opt.host, port=opt.port, reuse_port=True)
+
+        if opt.server == 'fastwsgi':
+            import fastwsgi
+            response_server = "FastWSGI"
+            response_add_date = False
+            fastwsgi.run(app, host=opt.host, port=opt.port, loglevel=opt.verbose)
+
+        if opt.server == 'socketify':
+            import socketify
+            response_server = None
+            response_add_date = False
+            socketify.WSGI(app).listen(opt.port, lambda config: logging.info(f"Listening on port http://localhost:{opt.port} now\n")).run()
+
+    def create_fork():
+        n = os.fork()
+        # n greater than 0 means parent process
+        if not n > 0:
+            run_app()
+
+    # fork limiting the cpu count - 1
+    for i in range(1, workers):
+        create_fork()
+
+    run_app()  # run app on the main process too :) 
+

+ 0 - 124
frameworks/Python/falcon/app_bjoern.py

@@ -1,124 +0,0 @@
-#!/usr/bin/env python
-import bjoern
-import falcon
-import re
-from email.utils import formatdate
-from db_orm import session, World, Fortune
-from helpers import load_template, FortuneTuple, generate_ids, sanitize
-from operator import attrgetter
-from random import randint
-
-
-# setup
-wsgi = app = falcon.App()
-
-# Note:
-# Bjoern doesn't provide any additional response headers like Date and Server
-# so we need to provide them manually.
-bjoern_version = [i for i in open('requirements-bjoern.txt', 'r') if re.search('bjoern', i)][0].strip().split('==')
-server_info = '{}/{}'.format(*bjoern_version).title()
-
-
-# resource endpoints
-class JSONResource(object):
-    def on_get(self, request, response):
-        response.set_header('Date', formatdate(timeval=None, localtime=False, usegmt=True))
-        response.set_header('Server', server_info)
-        response.media = {'message': "Hello, world!"}
-
-
-class SingleQuery(object):
-    @session(serializable=False)
-    def on_get(self, request, response):
-        wid = randint(1, 10000)
-        world = World[wid]
-        response.set_header('Date', formatdate(timeval=None, localtime=False, usegmt=True))
-        response.set_header('Server', server_info)
-        response.media = world.to_dict()
-
-
-class MultipleQueries(object):
-    @session(serializable=False)
-    def on_get(self, request, response, num):
-        num = sanitize(num)
-        worlds = [World[ident].to_dict() for ident in generate_ids(num)]
-        response.set_header('Date', formatdate(timeval=None, localtime=False, usegmt=True))
-        response.set_header('Server', server_info)
-        response.media = worlds
-
-
-class UpdateQueries(object):
-    @session(serializable=False)
-    def on_get(self, request, response, num):
-        num = sanitize(num)
-        ids = generate_ids(num)
-        ids.sort()
-        worlds = []
-        for item in ids:
-            world = World[item]
-            world.randomNumber = randint(1, 10000)
-            worlds.append({"id": world.id, "randomNumber": world.randomNumber})
-        response.set_header('Date', formatdate(timeval=None, localtime=False, usegmt=True))
-        response.set_header('Server', server_info)
-        response.media = worlds
-
-
-class Fortunes(object):
-    _template = load_template()
-
-    @session(serializable=False)
-    def on_get(self, request, response):
-        fortunes = [FortuneTuple(id=f.id, message=f.message) for f in Fortune.select()]
-        fortunes.append(FortuneTuple(id=0, message="Additional fortune added at request time."))
-        fortunes.sort(key=attrgetter("message"))
-        content = self._template.render(fortunes=fortunes)
-        response.set_header('Date', formatdate(timeval=None, localtime=False, usegmt=True))
-        response.set_header('Server', server_info)
-        response.content_type = falcon.MEDIA_HTML
-        response.text = content
-
-
-class PlaintextResource(object):
-    def on_get(self, request, response):
-        response.set_header('Date', formatdate(timeval=None, localtime=False, usegmt=True))
-        response.set_header('Server', server_info)
-        response.content_type = falcon.MEDIA_TEXT
-        response.text = 'Hello, world!'
-
-
-# register resources
-app.add_route("/json", JSONResource())
-app.add_route("/db", SingleQuery())
-app.add_route("/queries/{num}", MultipleQueries())
-app.add_route("/updates/{num}", UpdateQueries())
-app.add_route("/fortunes", Fortunes())
-app.add_route("/plaintext", PlaintextResource())
-
-
-if __name__ == "__main__":
-    import os
-    import multiprocessing
-
-    _is_travis = os.environ.get('TRAVIS') == 'true'
-
-    workers = int(multiprocessing.cpu_count())
-    if _is_travis:
-        workers = 2
-
-    host = '0.0.0.0'
-    port = 8080
-
-    def run_app():
-        bjoern.run(wsgi, host=host, port=port, reuse_port=True)
-
-    def create_fork():
-        n = os.fork()
-        # n greater than 0 means parent process
-        if not n > 0:
-            run_app()
-
-    # fork limiting the cpu count - 1
-    for i in range(1, workers):
-        create_fork()
-
-    run_app()  # run app on the main process too :)

+ 0 - 113
frameworks/Python/falcon/app_fastwsgi.py

@@ -1,113 +0,0 @@
-#!/usr/bin/env python
-import fastwsgi
-import falcon
-import re
-from db_orm import session, World, Fortune
-from helpers import load_template, FortuneTuple, generate_ids, sanitize
-from operator import attrgetter
-from random import randint
-
-
-# setup
-app = falcon.App()
-
-server_info = 'FastWSGI+Falcon'
-
-
-# resource endpoints
-class JSONResource(object):
-    def on_get(self, request, response):
-        response.set_header('Server', server_info)
-        response.media = {'message': "Hello, world!"}
-
-
-class SingleQuery(object):
-    @session(serializable=False)
-    def on_get(self, request, response):
-        wid = randint(1, 10000)
-        world = World[wid]
-        response.set_header('Server', server_info)
-        response.media = world.to_dict()
-
-
-class MultipleQueries(object):
-    @session(serializable=False)
-    def on_get(self, request, response, num):
-        num = sanitize(num)
-        worlds = [World[ident].to_dict() for ident in generate_ids(num)]
-        response.set_header('Server', server_info)
-        response.media = worlds
-
-
-class UpdateQueries(object):
-    @session(serializable=False)
-    def on_get(self, request, response, num):
-        num = sanitize(num)
-        ids = generate_ids(num)
-        ids.sort()
-        worlds = []
-        for item in ids:
-            world = World[item]
-            world.randomNumber = randint(1, 10000)
-            worlds.append({"id": world.id, "randomNumber": world.randomNumber})
-        response.set_header('Server', server_info)
-        response.media = worlds
-
-
-class Fortunes(object):
-    _template = load_template()
-
-    @session(serializable=False)
-    def on_get(self, request, response):
-        fortunes = [FortuneTuple(id=f.id, message=f.message) for f in Fortune.select()]
-        fortunes.append(FortuneTuple(id=0, message="Additional fortune added at request time."))
-        fortunes.sort(key=attrgetter("message"))
-        content = self._template.render(fortunes=fortunes)
-        response.set_header('Server', server_info)
-        response.content_type = falcon.MEDIA_HTML
-        response.text = content
-
-
-class PlaintextResource(object):
-    def on_get(self, request, response):
-        response.set_header('Server', server_info)
-        response.content_type = falcon.MEDIA_TEXT
-        response.text = 'Hello, world!'
-
-
-# register resources
-app.add_route("/json", JSONResource())
-app.add_route("/db", SingleQuery())
-app.add_route("/queries/{num}", MultipleQueries())
-app.add_route("/updates/{num}", UpdateQueries())
-app.add_route("/fortunes", Fortunes())
-app.add_route("/plaintext", PlaintextResource())
-
-
-if __name__ == "__main__":
-    import os
-    import multiprocessing
-
-    _is_travis = os.environ.get('TRAVIS') == 'true'
-
-    workers = int(multiprocessing.cpu_count())
-    if _is_travis:
-        workers = 2
-
-    host = '0.0.0.0'
-    port = 8080
-
-    def run_app():
-        fastwsgi.run(app, host=host, port=port, loglevel=0)
-
-    def create_fork():
-        n = os.fork()
-        # n greater than 0 means parent process
-        if not n > 0:
-            run_app()
-
-    # fork limiting the cpu count - 1
-    for i in range(1, workers):
-        create_fork()
-
-    run_app()  # run app on the main process too :)

+ 0 - 14
frameworks/Python/falcon/app_orjson.py

@@ -1,14 +0,0 @@
-#!/usr/bin/env python
-import orjson
-from falcon import media
-from app import wsgi
-
-
-# custom JSON handler
-JSONHandler = media.JSONHandler(dumps=orjson.dumps, loads=orjson.loads)
-extra_handlers = {
-    "application/json": JSONHandler,
-    "application/json; charset=UTF-8": JSONHandler
-}
-wsgi.req_options.media_handlers.update(extra_handlers)
-wsgi.resp_options.media_handlers.update(extra_handlers)

+ 0 - 23
frameworks/Python/falcon/app_waitress.py

@@ -1,23 +0,0 @@
-#!/usr/bin/env python
-from waitress import serve
-from app import wsgi
-from gunicorn_conf import bind, keepalive, workers
-import logging
-
-
-if __name__ == "__main__":
-    # waitress is very verbose while running, so we disable it
-    logging.basicConfig()
-    logging.getLogger().setLevel(logging.CRITICAL)
-    logging.disable(True)
-
-    serve(
-        app=wsgi,
-        listen=bind,
-        log_socket_errors=False,
-        threads=workers,
-        asyncore_use_poll=True,
-        expose_tracebacks=False,
-        connection_limit=128,
-        channel_timeout=keepalive,
-        _quiet=True)

+ 1 - 1
frameworks/Python/falcon/falcon-bjoern.dockerfile

@@ -7,4 +7,4 @@ RUN pip3 install -U pip; pip3 install -r /falcon/requirements-bjoern.txt
 
 EXPOSE 8080
 
-CMD ["python3", "app_bjoern.py"]
+CMD ["python3", "app.py", "-s", "bjoern"]

+ 1 - 1
frameworks/Python/falcon/falcon-fastwsgi.dockerfile

@@ -7,4 +7,4 @@ RUN pip3 install -U pip; pip3 install -r /falcon/requirements-fastwsgi.txt
 
 EXPOSE 8080
 
-CMD ["python3", "app_fastwsgi.py"]
+CMD ["python3", "app.py", "-s", "fastwsgi"]

+ 3 - 1
frameworks/Python/falcon/falcon-orjson.dockerfile

@@ -10,4 +10,6 @@ RUN pip3 install -U pip; \
 
 EXPOSE 8080
 
-CMD ["gunicorn", "app_orjson:wsgi", "-c", "gunicorn_conf.py"]
+ENV USE_ORJSON=1
+
+CMD ["gunicorn", "app:wsgi", "-c", "gunicorn_conf.py"]

+ 2 - 2
frameworks/Python/falcon/falcon-socketify-asgi.dockerfile

@@ -1,11 +1,11 @@
-FROM python:3.10-bullseye
+FROM python:3.9-bullseye
 
 RUN apt-get update; apt-get install libpq-dev python3-dev libuv1-dev -y
 COPY ./ /falcon
 WORKDIR /falcon
 RUN pip3 install -U pip; \
     pip3 install cython==0.29.26; \
-    pip3 install falcon==3.0.1 --no-binary :all:; \
+    pip3 install falcon==3.1.1 --no-binary :all:; \
     pip3 install -r /falcon/requirements-socketify.txt
 
 EXPOSE 8080

+ 1 - 1
frameworks/Python/falcon/falcon-socketify-wsgi-pypy3.dockerfile

@@ -7,4 +7,4 @@ RUN pip3 install -U pip; pip3 install -r /falcon/requirements-socketify-pypy.txt
 
 EXPOSE 8080
 
-CMD python app-socketify-wsgi.py
+CMD python app.py -s socketify

+ 3 - 3
frameworks/Python/falcon/falcon-socketify-wsgi.dockerfile

@@ -1,13 +1,13 @@
-FROM python:3.10-bullseye
+FROM python:3.9-bullseye
 
 RUN apt-get update; apt-get install libpq-dev python3-dev libuv1-dev -y
 COPY ./ /falcon
 WORKDIR /falcon
 RUN pip3 install -U pip; \
     pip3 install cython==0.29.26; \
-    pip3 install falcon==3.0.1 --no-binary :all:; \
+    pip3 install falcon==3.1.1 --no-binary :all:; \
     pip3 install -r /falcon/requirements-socketify.txt
 
 EXPOSE 8080
 
-CMD python app-socketify-wsgi.py
+CMD python app.py -s socketify

+ 1 - 1
frameworks/Python/falcon/falcon-waitress.dockerfile

@@ -7,4 +7,4 @@ RUN pip3 install -U pip; pip3 install -r /falcon/requirements.txt
 
 EXPOSE 8080
 
-CMD ["python", "app_waitress.py"]
+CMD ["python", "app.py", "-s", "waitress"]

+ 1 - 1
frameworks/Python/falcon/requirements-pypy.txt

@@ -1,4 +1,4 @@
-falcon==3.0.1
+falcon==3.1.1
 gunicorn==20.1.0
 jinja2==3.0.3
 pony==0.7.16

+ 1 - 1
frameworks/Python/falcon/requirements-socketify-pypy.txt

@@ -1,2 +1,2 @@
-falcon==3.0.1
+-r requirements-pypy.txt
 git+https://github.com/cirospaciari/socketify.py.git@main#socketify

+ 1 - 2
frameworks/Python/falcon/requirements-socketify.txt

@@ -1,3 +1,2 @@
-Cython==0.29.26
-falcon==3.0.1
+-r requirements.txt
 git+https://github.com/cirospaciari/socketify.py.git@main#socketify