How to run Python WSGIServer using gunicorn?

Viewed 82

I have implemented a simple Flask-like framework using HTTPServer and BaseHTTPRequestHandler from http.server for educational purposes.

I have found out that I can replace HTTPServer with WSGIServer from wsgiref.simple_server and the code runs without any other modification.

The run method of my implementation works as follows:

def run(self, host: str, port: int):
        web_server = WSGIServer((host, port), Request)
        try:
            print("Server is running at port", port)
            web_server.serve_forever()
        except KeyboardInterrupt:
            web_server.server_close()
            print("Server stopped")

When I read the gunicorn docs, I have found out that gunicorn expects a function like this:

def app(environ, start_response):
    """Simplest possible application object"""
    data = b'Hello, World!\n'
    status = '200 OK'
    response_headers = [
        ('Content-type', 'text/plain'),
        ('Content-Length', str(len(data)))
    ]
    start_response(status, response_headers)
    return iter([data])

And that wsgiref.simple_server.make_server takes a function like the above and instantiates a WSGIServer and calls the serve_forever() method like I did in my implementation.

I have tried to return the app from thr WSGIServer from the WSGIServer.get_app() method but it returns None.

I would like to know how to run my WSGIServer class using gunicorn from the terminal.

1 Answers

If you have a framework-like app, then one best way to serve it is to wrap the framework entry point as a callable class, and pass this as a whole to gunicorn.

Here are examples for Waitress web server and Gunicorn web server how to make it serve the same WSGI app


# Router.__call__() is the entry point for the request processing
def setup_app() -> Router:
  
    context.init_console_logging()

    app = create_app(production=True, oracle_context=context)

    check_oracle_db()
    check_web_db()

    from tradingstrategy import __version__
    from .api import version as api_version
    logger.info("Server version is %s, client library version %s", api_version, __version__)
  return app


# Waitress main entry point
def main():

    app = setup_app()

    port = int(os.environ.get("BACKEND_PORT", 3456))

    scheme = os.environ.get("WSGI_SCHEME", "http")

    logger.info("Matilda backend server starting at port %d", port)

    serve(app,
          host='127.0.0.1',
          trusted_proxy="127.0.0.1",
          threads=32,
          port=port,
          trusted_proxy_headers="forwarded",
          url_scheme=scheme,
          url_prefix='/api')


def gunicorn_entry_point() -> Router:
    """Gunicorn calls this function for every worker process.

    To run::

        gunicorn --bind 127.0.0.1:3456 --workers 1 --threads 1 "backend.server.server:gunicorn_entry_point()"

    """
    app = setup_app()
    return app

See Pyramid Router implementation for WSGI app. It is a good example to look upon if you are unsure how to deal with WSGI framework.


class Router:

    # ...

    def __call__(self, environ, start_response):
        """
        Accept ``environ`` and ``start_response``; create a
        :term:`request` and route the request to a :app:`Pyramid`
        view based on introspection of :term:`view configuration`
        within the application registry; call ``start_response`` and
        return an iterable.
        """
        response = self.execution_policy(environ, self)
        return response(environ, start_response)


Related