Browse Source

[Python] Add Socketify.py ASGI and WSGI, add Falcon with ASGI and WSGi using socketify.py (#7761)

* Added a benchmark test for the socketify.py Python/PyPy web framework

* removed alpine docker

* updated readme.md

* No need to manually add Date header anymore on Socketify.py

* updated socketify.py benchmark to use object factories to increase performance

* add object factory to app-python3.py in socketify

* [Python] Update socketify.py with ASGI and SSGI, add Falcon ASGI SSGI with socketify.py

* fix merge erros

* remove \n from plaintext in socketify.py-wsgi plaintext
Ciro Spaciari 2 years ago
parent
commit
0143cbc29b

+ 48 - 0
frameworks/Python/falcon/app-socketify-asgi.py

@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+import falcon.asgi
+
+from socketify import ASGI
+import os
+import multiprocessing
+import logging
+
+# setup
+asgi = app = falcon.asgi.App()
+
+# resource endpoints
+class JSONResource(object):
+    async def on_get(self, request, response):
+        response.media = {'message': "Hello, world!"}
+
+class PlaintextResource(object):
+    async 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():
+    ASGI(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, workers):
+    create_fork()
+
+run_app()  # run app on the main process too :)

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

@@ -0,0 +1,49 @@
+#!/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 :)

+ 76 - 0
frameworks/Python/falcon/benchmark_config.json

@@ -24,6 +24,82 @@
       "notes": "cythonized",
       "notes": "cythonized",
       "versus": "wsgi"
       "versus": "wsgi"
     },
     },
+    "socketify-wsgi": {
+      "json_url": "/json",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "Falcon",
+      "language": "Python",
+      "flavor": "Python3",
+      "orm": "Full",
+      "platform": "WSGI",
+      "webserver": "Socketify.py",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Falcon [socketify.py WSGI]",
+      "notes": "cythonized",
+      "versus": "wsgi"
+    },
+    "socketify-wsgi-pypy3": {
+      "json_url": "/json",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "Falcon",
+      "language": "Python",
+      "flavor": "PyPy3",
+      "orm": "Full",
+      "platform": "WSGI",
+      "webserver": "Socketify.py",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Falcon [socketify.py WSGI PyPy3]",
+      "notes": "",
+      "versus": "wsgi"
+    },
+    "socketify-asgi-pypy3": {
+      "json_url": "/json",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "Falcon",
+      "language": "Python",
+      "flavor": "PyPy3",
+      "orm": "Full",
+      "platform": "ASGI",
+      "webserver": "Socketify.py",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Falcon [socketify.py ASGI PyPy3]",
+      "notes": "",
+      "versus": "asgi"
+    },
+    "socketify-asgi": {
+      "json_url": "/json",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "Falcon",
+      "language": "Python",
+      "flavor": "Python3",
+      "orm": "Full",
+      "platform": "ASGI",
+      "webserver": "Socketify.py",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Falcon [socketify.py ASGI]",
+      "notes": "cythonized",
+      "versus": "asgi"
+    },
     "bjoern": {
     "bjoern": {
       "json_url": "/json",
       "json_url": "/json",
       "db_url": "/db",
       "db_url": "/db",

+ 76 - 0
frameworks/Python/falcon/config.toml

@@ -93,6 +93,82 @@ display.name = "Falcon [Meinheld-Orjson]"
 notes = "cythonized+orjson"
 notes = "cythonized+orjson"
 versus = "wsgi"
 versus = "wsgi"
 
 
+[socketify-wsgi]
+json.url = "/json"
+plaintext.url = "/plaintext"
+port = 8080
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+framework = "Falcon"
+language = "Python"
+flavor = "Python3"
+orm = "Full"
+platform = "WSGI"
+webserver = "Socketify.py"
+os = "Linux"
+database.os = "Linux"
+display.name = "Falcon [socketify.py WSGI]"
+notes = "cythonized"
+versus = "wsgi"
+
+[socketify-asgi]
+json.url = "/json"
+plaintext.url = "/plaintext"
+port = 8080
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+framework = "Falcon"
+language = "Python"
+flavor = "Python3"
+orm = "Full"
+platform = "ASGI"
+webserver = "Socketify.py"
+os = "Linux"
+database.os = "Linux"
+display.name = "Falcon [socketify.py ASGI]"
+notes = "cythonized"
+versus = "asgi"
+
+[socketify-asgi-pypy3]
+json.url = "/json"
+plaintext.url = "/plaintext"
+port = 8080
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+framework = "Falcon"
+language = "Python"
+flavor = "PyPy3"
+orm = "Full"
+platform = "ASGI"
+webserver = "Socketify.py"
+os = "Linux"
+database.os = "Linux"
+display.name = "Falcon [socketify.py ASGI Pypy3]"
+notes = ""
+versus = "asgi"
+
+[socketify-wsgi-pypy3]
+json.url = "/json"
+plaintext.url = "/plaintext"
+port = 8080
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+framework = "Falcon"
+language = "Python"
+flavor = "PyPy3"
+orm = "Full"
+platform = "WSGI"
+webserver = "Socketify.py"
+os = "Linux"
+database.os = "Linux"
+display.name = "Falcon [socketify.py WSGI Pypy3]"
+notes = ""
+versus = "wsgi"
+
 [pypy3]
 [pypy3]
 json.url = "/json"
 json.url = "/json"
 db.url = "/db"
 db.url = "/db"

+ 10 - 0
frameworks/Python/falcon/falcon-socketify-asgi-pypy3.dockerfile

@@ -0,0 +1,10 @@
+FROM pypy: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 -r /falcon/requirements-socketify-pypy.txt
+
+EXPOSE 8080
+
+CMD python app-socketify-asgi.py

+ 13 - 0
frameworks/Python/falcon/falcon-socketify-asgi.dockerfile

@@ -0,0 +1,13 @@
+FROM python:3.10-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 -r /falcon/requirements-socketify.txt
+
+EXPOSE 8080
+
+CMD python app-socketify-asgi.py

+ 10 - 0
frameworks/Python/falcon/falcon-socketify-wsgi-pypy3.dockerfile

@@ -0,0 +1,10 @@
+FROM pypy: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 -r /falcon/requirements-socketify-pypy.txt
+
+EXPOSE 8080
+
+CMD python app-socketify-wsgi.py

+ 13 - 0
frameworks/Python/falcon/falcon-socketify-wsgi.dockerfile

@@ -0,0 +1,13 @@
+FROM python:3.10-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 -r /falcon/requirements-socketify.txt
+
+EXPOSE 8080
+
+CMD python app-socketify-wsgi.py

+ 2 - 0
frameworks/Python/falcon/requirements-socketify-pypy.txt

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

+ 3 - 0
frameworks/Python/falcon/requirements-socketify.txt

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

+ 2 - 6
frameworks/Python/socketify.py/app-python3.py

@@ -1,17 +1,14 @@
 
 
-import threading
-import time
 import os
 import os
 
 
 from ujson import dumps as json
 from ujson import dumps as json
-from datetime import datetime
 
 
 from socketify import App
 from socketify import App
 
 
 def plaintext(res, req):
 def plaintext(res, req):
     res.write_header("Server", "socketify")
     res.write_header("Server", "socketify")
     res.write_header("Content-Type", "text/plain")
     res.write_header("Content-Type", "text/plain")
-    res.end("Hello, World!")
+    res.end(b'Hello, World!')
 
 
 def applicationjson(res, req):
 def applicationjson(res, req):
     res.write_header("Server", "socketify")
     res.write_header("Server", "socketify")
@@ -20,8 +17,7 @@ def applicationjson(res, req):
 
 
 
 
 def run_app():
 def run_app():
-    app = App(request_response_factory_max_itens=200_000)
-    
+    app = app = App(None, 200_000, 0)
     app.get("/", plaintext)
     app.get("/", plaintext)
     app.get("/json", applicationjson)
     app.get("/json", applicationjson)
     app.get("/plaintext", plaintext)
     app.get("/plaintext", plaintext)

+ 3 - 11
frameworks/Python/socketify.py/app.py

@@ -1,17 +1,13 @@
 
 
-import threading
-import time
 import os
 import os
 
 
-from datetime import datetime
-
 from socketify import App
 from socketify import App
 
 
 
 
 def plaintext(res, req):
 def plaintext(res, req):
     res.write_header("Server", "socketify")
     res.write_header("Server", "socketify")
     res.write_header("Content-Type", "text/plain")
     res.write_header("Content-Type", "text/plain")
-    res.end("Hello, World!")
+    res.end(b'Hello, World!')
 
 
 def applicationjson(res, req):
 def applicationjson(res, req):
     res.write_header("Server", "socketify")
     res.write_header("Server", "socketify")
@@ -19,7 +15,7 @@ def applicationjson(res, req):
 
 
 
 
 def run_app():
 def run_app():
-    app = App(request_response_factory_max_itens=200_000)
+    app = App(None, 200_000, 0)
     app.get("/", plaintext)
     app.get("/", plaintext)
     app.get("/json", applicationjson)
     app.get("/json", applicationjson)
     app.get("/plaintext", plaintext)
     app.get("/plaintext", plaintext)
@@ -45,8 +41,4 @@ for index in range(WORKER_COUNT):
 
 
 run_app()
 run_app()
 #sudo ./tfb --mode benchmark --test socketify.py --type plaintext
 #sudo ./tfb --mode benchmark --test socketify.py --type plaintext
-#sudo ./tfb --mode benchmark --test socketify.py --type json --network=tfb
-
-# 770k with timer + agressive cpu
-# 851k without timer
-# ???k with timer + relaxed cpu
+#sudo ./tfb --mode benchmark --test socketify.py --type json --network=tfb

+ 73 - 1
frameworks/Python/socketify.py/benchmark_config.json

@@ -34,7 +34,79 @@
             "webserver": "socketify.py-python3",
             "webserver": "socketify.py-python3",
             "os": "Linux",
             "os": "Linux",
             "database_os": "Linux",
             "database_os": "Linux",
-            "display_name": "socketify.py-python3",
+            "display_name": "socketify.py [Python3]",
+            "notes": ""
+        },
+        "asgi": {
+            "json_url": "/json",
+            "plaintext_url": "/plaintext",
+            "port": 3000,
+            "approach": "Realistic",
+            "classification": "Micro",
+            "database": "None",
+            "framework": "None",
+            "language": "Python",
+            "flavor": "PyPy3",
+            "orm": "Raw",
+            "platform": "None",
+            "webserver": "socketify.py-asgi",
+            "os": "Linux",
+            "database_os": "Linux",
+            "display_name": "socketify.py [ASGI]",
+            "notes": ""
+        },
+        "asgi-python3": {
+            "json_url": "/json",
+            "plaintext_url": "/plaintext",
+            "port": 3000,
+            "approach": "Realistic",
+            "classification": "Micro",
+            "database": "None",
+            "framework": "None",
+            "language": "Python",
+            "flavor": "Python3",
+            "orm": "Raw",
+            "platform": "None",
+            "webserver": "socketify.py-asgi",
+            "os": "Linux",
+            "database_os": "Linux",
+            "display_name": "socketify.py [ASGI Python3]",
+            "notes": ""
+        },
+        "wsgi": {
+            "json_url": "/json",
+            "plaintext_url": "/plaintext",
+            "port": 3000,
+            "approach": "Realistic",
+            "classification": "Micro",
+            "database": "None",
+            "framework": "None",
+            "language": "Python",
+            "flavor": "PyPy3",
+            "orm": "Raw",
+            "platform": "None",
+            "webserver": "socketify.py-wsgi",
+            "os": "Linux",
+            "database_os": "Linux",
+            "display_name": "socketify.py [WSGI]",
+            "notes": ""
+        },
+        "wsgi-python3": {
+            "json_url": "/json",
+            "plaintext_url": "/plaintext",
+            "port": 3000,
+            "approach": "Realistic",
+            "classification": "Micro",
+            "database": "None",
+            "framework": "None",
+            "language": "Python",
+            "flavor": "Python3",
+            "orm": "Raw",
+            "platform": "None",
+            "webserver": "socketify.py-wsgi",
+            "os": "Linux",
+            "database_os": "Linux",
+            "display_name": "socketify.py [WSGI Python3]",
             "notes": ""
             "notes": ""
         }
         }
     }]
     }]

+ 52 - 0
frameworks/Python/socketify.py/config.toml

@@ -25,4 +25,56 @@ os = "Linux"
 orm = "Raw"
 orm = "Raw"
 platform = "None"
 platform = "None"
 webserver = "socketify.py-python3"
 webserver = "socketify.py-python3"
+versus = "None"
+
+[wsgi]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+approach = "Realistic"
+classification = "Micro"
+database = "None"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "None"
+webserver = "socketify.py-wsgi"
+versus = "None"
+
+[wsgi-python3]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+approach = "Realistic"
+classification = "Micro"
+database = "None"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "None"
+webserver = "socketify.py-wsgi"
+versus = "None"
+
+[asgi]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+approach = "Realistic"
+classification = "Micro"
+database = "None"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "None"
+webserver = "socketify.py-asgi"
+versus = "None"
+
+[asgi-python3]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+approach = "Realistic"
+classification = "Micro"
+database = "None"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "None"
+webserver = "socketify.py-asgi"
 versus = "None"
 versus = "None"

+ 59 - 0
frameworks/Python/socketify.py/raw-asgi.py

@@ -0,0 +1,59 @@
+from socketify import ASGI
+import os
+try:
+    from ujson import dumps as json
+except:
+    from json import dumps as json
+    
+async def app(scope, receive, send):
+    assert scope['type'] == 'http'
+    path = scope['path']
+    if path == "/plaintext":
+        await send({
+            'type': 'http.response.start',
+            'status': 200,
+            'headers': [
+                [b'content-type', b'text/plain'],
+            ],
+        })
+        await send({
+            'type': 'http.response.body',
+            'body': b'Hello, world!',
+        })
+        return
+    if path == "/json":
+        await send({
+            'type': 'http.response.start',
+            'status': 200,
+            'headers': [
+                [b'content-type', b'application/json'],
+            ],
+        })
+        await send({
+            'type': 'http.response.body',
+            'body': json({"message":"Hello, World!"}).encode('utf8'),
+        })
+        
+
+
+def run_app():
+    ASGI(app).listen(3000, lambda config: print(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()
+
+def get_worker_count():
+    try:
+        return int(os.environ["WORKER_COUNT"])
+    except:
+        return 2
+
+WORKER_COUNT = get_worker_count() - 1
+
+for index in range(WORKER_COUNT):
+    create_fork()
+
+run_app()

+ 40 - 0
frameworks/Python/socketify.py/raw-wsgi.py

@@ -0,0 +1,40 @@
+from socketify import WSGI
+import os
+
+try:
+    from ujson import dumps as json
+except:
+    from json import dumps as json
+    
+def app(environ, start_response):
+    path = environ["PATH_INFO"]
+    if path == "/plaintext":
+        start_response('200 OK', [('Content-Type', 'text/plain')])
+        yield b'Hello, World!'
+        return
+    if path == "/json":
+        start_response('200 OK', [('Content-Type', 'application/json')])
+        yield json({"message":"Hello, World!"}).encode('utf8')
+        return
+    
+def run_app():
+    WSGI(app).listen(3000, lambda config: print(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()
+
+def get_worker_count():
+    try:
+        return int(os.environ["WORKER_COUNT"])
+    except:
+        return 2
+
+WORKER_COUNT = get_worker_count() - 1
+
+for index in range(WORKER_COUNT):
+    create_fork()
+
+run_app()

+ 15 - 0
frameworks/Python/socketify.py/socketify.py-asgi-python3.dockerfile

@@ -0,0 +1,15 @@
+FROM python:3.11-bullseye
+
+WORKDIR /usr/src/app
+
+COPY requirements-python3.txt ./
+RUN apt-get update
+RUN apt install libuv1-dev -y
+RUN pip install --no-cache-dir ujson
+RUN pip install --no-cache-dir -r requirements-python3.txt
+
+COPY . .
+
+EXPOSE 3000
+
+CMD WORKER_COUNT=$(nproc) python ./raw-asgi.py

+ 15 - 0
frameworks/Python/socketify.py/socketify.py-asgi.dockerfile

@@ -0,0 +1,15 @@
+FROM pypy:3.9-bullseye
+
+WORKDIR /usr/src/app
+
+COPY requirements.txt ./
+
+RUN apt-get update
+RUN apt install libuv1-dev -y
+RUN pip install --no-cache-dir -r requirements.txt
+
+COPY . .
+
+EXPOSE 3000
+
+CMD WORKER_COUNT=$(nproc) pypy3 ./raw-asgi.py

+ 1 - 1
frameworks/Python/socketify.py/socketify.py-python3.dockerfile

@@ -1,4 +1,4 @@
-FROM python:3.11-rc
+FROM python:3.11-bullseye
 
 
 WORKDIR /usr/src/app
 WORKDIR /usr/src/app
 
 

+ 15 - 0
frameworks/Python/socketify.py/socketify.py-wsgi-python3.dockerfile

@@ -0,0 +1,15 @@
+FROM python:3.11-bullseye
+
+WORKDIR /usr/src/app
+
+COPY requirements-python3.txt ./
+RUN apt-get update
+RUN apt install libuv1-dev -y
+RUN pip install --no-cache-dir ujson
+RUN pip install --no-cache-dir -r requirements-python3.txt
+
+COPY . .
+
+EXPOSE 3000
+
+CMD WORKER_COUNT=$(nproc) python ./raw-wsgi.py

+ 15 - 0
frameworks/Python/socketify.py/socketify.py-wsgi.dockerfile

@@ -0,0 +1,15 @@
+FROM pypy:3.9-bullseye
+
+WORKDIR /usr/src/app
+
+COPY requirements.txt ./
+
+RUN apt-get update
+RUN apt install libuv1-dev -y
+RUN pip install --no-cache-dir -r requirements.txt
+
+COPY . .
+
+EXPOSE 3000
+
+CMD WORKER_COUNT=$(nproc) pypy3 ./raw-wsgi.py

+ 1 - 1
frameworks/Python/socketify.py/socketify.py.dockerfile

@@ -1,4 +1,4 @@
-FROM pypy:3.8
+FROM pypy:3.9-bullseye
 
 
 WORKDIR /usr/src/app
 WORKDIR /usr/src/app