From 09cf831855492889fd8e3b707a7cc65e8afe5f71 Mon Sep 17 00:00:00 2001
From: Bastien Abadie <bastien@nextcairn.com>
Date: Wed, 15 Jul 2020 13:53:49 +0000
Subject: [PATCH] Use gunicorn to serve the API through a management command

---
 Dockerfile                                    | 15 +---
 .../documents/management/commands/gunicorn.py | 70 +++++++++++++++++++
 2 files changed, 73 insertions(+), 12 deletions(-)
 create mode 100644 arkindex/documents/management/commands/gunicorn.py

diff --git a/Dockerfile b/Dockerfile
index e45b66358d..06464155b1 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -32,7 +32,7 @@ RUN \
 # Install arkindex and its deps
 # Uses a source archive instead of full local copy to speedup docker build
 COPY --from=build /build/dist/arkindex-*.tar.gz /tmp/arkindex.tar.gz
-RUN pip install /tmp/arkindex.tar.gz && rm /tmp/arkindex.tar.gz
+RUN pip install gunicorn /tmp/arkindex.tar.gz && rm /tmp/arkindex.tar.gz
 
 # Allow access to static files directory
 RUN mkdir -p /backend_static
@@ -42,15 +42,6 @@ RUN chown -R ark:teklia /backend_static
 COPY VERSION /etc/arkindex.version
 
 # Run with Daphne
+ENV PORT 80
 EXPOSE 80
-CMD [ \
-  "daphne", \
-  "--http-timeout=30", \
-  "--websocket_connect_timeout=10", \
-  "--websocket_timeout=120", \
-  "--ping-timeout=120", \
-  "--application-close-timeout=3", \
-  "--verbosity=1", \
-  "--bind=0.0.0.0", \
-  "--port=80", \
-  "arkindex.project.asgi:application"]
+CMD ["manage.py", "gunicorn", "--host=0.0.0.0"]
diff --git a/arkindex/documents/management/commands/gunicorn.py b/arkindex/documents/management/commands/gunicorn.py
new file mode 100644
index 0000000000..8a1663f40d
--- /dev/null
+++ b/arkindex/documents/management/commands/gunicorn.py
@@ -0,0 +1,70 @@
+from django.core.management.base import BaseCommand, CommandError
+import sys
+import os
+import multiprocessing
+from django.core.wsgi import get_wsgi_application
+
+import logging
+
+logging.basicConfig(
+    level=logging.INFO,
+    format="%(asctime)s [%(levelname)s] %(message)s",
+)
+logger = logging.getLogger()
+
+
+class Command(BaseCommand):
+    help = "Run Arkindex API server through Gunicorn"
+
+    def add_arguments(self, parser):
+        parser.add_argument(
+            "--host",
+            type=str,
+            help="Host to bind gunicorn",
+            default=os.environ.get("HOST", "localhost"),
+        )
+        parser.add_argument(
+            "--port",
+            type=int,
+            help="Port to bind gunicorn",
+            default=int(os.environ.get("PORT", 8000)),
+        )
+        parser.add_argument(
+            "--max-workers",
+            help="Add a limit to the number of workers",
+            type=int,
+            default=int(os.environ.get("MAX_WORKERS", 0)),
+        )
+
+    def handle(self, host, port, max_workers, *args, **kwargs):
+        try:
+            from gunicorn.app.base import Application
+        except ImportError:
+            raise CommandError("Gunicorn is not available")
+
+        # Calc max workers
+        workers = (multiprocessing.cpu_count() * 2) + 1
+        if max_workers > 0:
+            workers = min(workers, max_workers)
+
+        # Build bind string
+        bind = f"{host}:{port}"
+        logger.info(f"Running server on {bind} with {workers} workers")
+
+        # Do not send out CLI args to gunicorn as they are not compatible
+        # and we override directly the configuration to pass the WSGI application
+        # This is needed to run through Nuitka
+        sys.argv = sys.argv[:2]
+
+        class ArkindexServer(Application):
+            """Run the Django WSGI app through gunicorn"""
+            def init(self, *args, **kwargs):
+                return {
+                    "bind": bind,
+                    "workers": workers
+                }
+
+            def load(self):
+                return get_wsgi_application()
+
+        ArkindexServer().run()
-- 
GitLab