rewrite deployment docs
This commit is contained in:
parent
9398630a8f
commit
2ea77c2782
15 changed files with 807 additions and 829 deletions
66
docs/deploying/apache-httpd.rst
Normal file
66
docs/deploying/apache-httpd.rst
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
Apache httpd
|
||||
============
|
||||
|
||||
`Apache httpd`_ is a fast, production level HTTP server. When serving
|
||||
your application with one of the WSGI servers listed in :doc:`index`, it
|
||||
is often good or necessary to put a dedicated HTTP server in front of
|
||||
it. This "reverse proxy" can handle incoming requests, TLS, and other
|
||||
security and performance concerns better than the WSGI server.
|
||||
|
||||
httpd can be installed using your system package manager, or a pre-built
|
||||
executable for Windows. Installing and running httpd itself is outside
|
||||
the scope of this doc. This page outlines the basics of configuring
|
||||
httpd to proxy your application. Be sure to read its documentation to
|
||||
understand what features are available.
|
||||
|
||||
.. _Apache httpd: https://httpd.apache.org/
|
||||
|
||||
|
||||
Domain Name
|
||||
-----------
|
||||
|
||||
Acquiring and configuring a domain name is outside the scope of this
|
||||
doc. In general, you will buy a domain name from a registrar, pay for
|
||||
server space with a hosting provider, and then point your registrar
|
||||
at the hosting provider's name servers.
|
||||
|
||||
To simulate this, you can also edit your ``hosts`` file, located at
|
||||
``/etc/hosts`` on Linux. Add a line that associates a name with the
|
||||
local IP.
|
||||
|
||||
Modern Linux systems may be configured to treat any domain name that
|
||||
ends with ``.localhost`` like this without adding it to the ``hosts``
|
||||
file.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``/etc/hosts``
|
||||
|
||||
127.0.0.1 hello.localhost
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
The httpd configuration is located at ``/etc/httpd/conf/httpd.conf`` on
|
||||
Linux. It may be different depending on your operating system. Check the
|
||||
docs and look for ``httpd.conf``.
|
||||
|
||||
Remove or comment out any existing ``DocumentRoot`` directive. Add the
|
||||
config lines below. We'll assume the WSGI server is listening locally at
|
||||
``http://127.0.0.1:8000``.
|
||||
|
||||
.. code-block:: apache
|
||||
:caption: ``/etc/httpd/conf/httpd.conf``
|
||||
|
||||
LoadModule proxy_module modules/mod_proxy.so
|
||||
LoadModule proxy_http_module modules/mod_proxy_http.so
|
||||
ProxyPass / http://127.0.0.1:8000/
|
||||
RequestHeader set X-Forwarded-Proto http
|
||||
RequestHeader set X-Forwarded-Prefix /
|
||||
|
||||
The ``LoadModule`` lines might already exist. If so, make sure they are
|
||||
uncommented instead of adding them manually.
|
||||
|
||||
Then :doc:`proxy_fix` so that your application uses the ``X-Forwarded``
|
||||
headers. ``X-Forwarded-For`` and ``X-Forwarded-Host`` are automatically
|
||||
set by ``ProxyPass``.
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
.. _asgi:
|
||||
|
||||
ASGI
|
||||
====
|
||||
|
||||
|
|
|
|||
|
|
@ -1,61 +0,0 @@
|
|||
CGI
|
||||
===
|
||||
|
||||
If all other deployment methods do not work, CGI will work for sure.
|
||||
CGI is supported by all major servers but usually has a sub-optimal
|
||||
performance.
|
||||
|
||||
This is also the way you can use a Flask application on Google's `App
|
||||
Engine`_, where execution happens in a CGI-like environment.
|
||||
|
||||
.. admonition:: Watch Out
|
||||
|
||||
Please make sure in advance that any ``app.run()`` calls you might
|
||||
have in your application file are inside an ``if __name__ ==
|
||||
'__main__':`` block or moved to a separate file. Just make sure it's
|
||||
not called because this will always start a local WSGI server which
|
||||
we do not want if we deploy that application to CGI / app engine.
|
||||
|
||||
With CGI, you will also have to make sure that your code does not contain
|
||||
any ``print`` statements, or that ``sys.stdout`` is overridden by something
|
||||
that doesn't write into the HTTP response.
|
||||
|
||||
Creating a `.cgi` file
|
||||
----------------------
|
||||
|
||||
First you need to create the CGI application file. Let's call it
|
||||
:file:`yourapplication.cgi`::
|
||||
|
||||
#!/usr/bin/python
|
||||
from wsgiref.handlers import CGIHandler
|
||||
from yourapplication import app
|
||||
|
||||
CGIHandler().run(app)
|
||||
|
||||
Server Setup
|
||||
------------
|
||||
|
||||
Usually there are two ways to configure the server. Either just copy the
|
||||
``.cgi`` into a :file:`cgi-bin` (and use `mod_rewrite` or something similar to
|
||||
rewrite the URL) or let the server point to the file directly.
|
||||
|
||||
In Apache for example you can put something like this into the config:
|
||||
|
||||
.. sourcecode:: apache
|
||||
|
||||
ScriptAlias /app /path/to/the/application.cgi
|
||||
|
||||
On shared webhosting, though, you might not have access to your Apache config.
|
||||
In this case, a file called ``.htaccess``, sitting in the public directory
|
||||
you want your app to be available, works too but the ``ScriptAlias`` directive
|
||||
won't work in that case:
|
||||
|
||||
.. sourcecode:: apache
|
||||
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_FILENAME} !-f # Don't interfere with static files
|
||||
RewriteRule ^(.*)$ /path/to/the/application.cgi/$1 [L]
|
||||
|
||||
For more information consult the documentation of your webserver.
|
||||
|
||||
.. _App Engine: https://cloud.google.com/appengine/docs/
|
||||
80
docs/deploying/eventlet.rst
Normal file
80
docs/deploying/eventlet.rst
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
eventlet
|
||||
========
|
||||
|
||||
Prefer using :doc:`gunicorn` with eventlet workers rather than using
|
||||
`eventlet`_ directly. Gunicorn provides a much more configurable and
|
||||
production-tested server.
|
||||
|
||||
`eventlet`_ allows writing asynchronous, coroutine-based code that looks
|
||||
like standard synchronous Python. It uses `greenlet`_ to enable task
|
||||
switching without writing ``async/await`` or using ``asyncio``.
|
||||
|
||||
:doc:`gevent` is another library that does the same thing. Certain
|
||||
dependencies you have, or other considerations, may affect which of the
|
||||
two you choose to use.
|
||||
|
||||
eventlet provides a WSGI server that can handle many connections at once
|
||||
instead of one per worker process. You must actually use eventlet in
|
||||
your own code to see any benefit to using the server.
|
||||
|
||||
.. _eventlet: https://eventlet.net/
|
||||
.. _greenlet: https://greenlet.readthedocs.io/en/latest/
|
||||
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
When using eventlet, greenlet>=1.0 is required, otherwise context locals
|
||||
such as ``request`` will not work as expected. When using PyPy,
|
||||
PyPy>=7.3.7 is required.
|
||||
|
||||
Create a virtualenv, install your application, then install
|
||||
``eventlet``.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ cd hello-app
|
||||
$ python -m venv venv
|
||||
$ . venv/bin/activate
|
||||
$ pip install . # install your application
|
||||
$ pip install eventlet
|
||||
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
To use eventlet to serve your application, write a script that imports
|
||||
its ```wsgi.server``, as well as your app or app factory.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``wsgi.py``
|
||||
|
||||
import eventlet
|
||||
from eventlet import wsgi
|
||||
from hello import create_app
|
||||
|
||||
app = create_app()
|
||||
wsgi.server(eventlet.listen(("127.0.0.1", 8000), app)
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ python wsgi.py
|
||||
(x) wsgi starting up on http://127.0.0.1:8000
|
||||
|
||||
|
||||
Binding Externally
|
||||
------------------
|
||||
|
||||
eventlet should not be run as root because it would cause your
|
||||
application code to run as root, which is not secure. However, this
|
||||
means it will not be possible to bind to port 80 or 443. Instead, a
|
||||
reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used
|
||||
in front of eventlet.
|
||||
|
||||
You can bind to all external IPs on a non-privileged port by using
|
||||
``0.0.0.0`` in the server arguments shown in the previous section.
|
||||
Don't do this when using a reverse proxy setup, otherwise it will be
|
||||
possible to bypass the proxy.
|
||||
|
||||
``0.0.0.0`` is not a valid address to navigate to, you'd use a specific
|
||||
IP address in your browser.
|
||||
|
|
@ -1,238 +0,0 @@
|
|||
FastCGI
|
||||
=======
|
||||
|
||||
FastCGI is a deployment option on servers like `nginx`_, `lighttpd`_, and
|
||||
`cherokee`_; see :doc:`uwsgi` and :doc:`wsgi-standalone` for other options.
|
||||
To use your WSGI application with any of them you will need a FastCGI
|
||||
server first. The most popular one is `flup`_ which we will use for
|
||||
this guide. Make sure to have it installed to follow along.
|
||||
|
||||
.. admonition:: Watch Out
|
||||
|
||||
Please make sure in advance that any ``app.run()`` calls you might
|
||||
have in your application file are inside an ``if __name__ ==
|
||||
'__main__':`` block or moved to a separate file. Just make sure it's
|
||||
not called because this will always start a local WSGI server which
|
||||
we do not want if we deploy that application to FastCGI.
|
||||
|
||||
Creating a `.fcgi` file
|
||||
-----------------------
|
||||
|
||||
First you need to create the FastCGI server file. Let's call it
|
||||
`yourapplication.fcgi`::
|
||||
|
||||
#!/usr/bin/python
|
||||
from flup.server.fcgi import WSGIServer
|
||||
from yourapplication import app
|
||||
|
||||
if __name__ == '__main__':
|
||||
WSGIServer(app).run()
|
||||
|
||||
This is enough for Apache to work, however nginx and older versions of
|
||||
lighttpd need a socket to be explicitly passed to communicate with the
|
||||
FastCGI server. For that to work you need to pass the path to the
|
||||
socket to the :class:`~flup.server.fcgi.WSGIServer`::
|
||||
|
||||
WSGIServer(application, bindAddress='/path/to/fcgi.sock').run()
|
||||
|
||||
The path has to be the exact same path you define in the server
|
||||
config.
|
||||
|
||||
Save the :file:`yourapplication.fcgi` file somewhere you will find it again.
|
||||
It makes sense to have that in :file:`/var/www/yourapplication` or something
|
||||
similar.
|
||||
|
||||
Make sure to set the executable bit on that file so that the servers
|
||||
can execute it:
|
||||
|
||||
.. sourcecode:: text
|
||||
|
||||
$ chmod +x /var/www/yourapplication/yourapplication.fcgi
|
||||
|
||||
Configuring Apache
|
||||
------------------
|
||||
|
||||
The example above is good enough for a basic Apache deployment but your
|
||||
`.fcgi` file will appear in your application URL e.g.
|
||||
``example.com/yourapplication.fcgi/news/``. There are few ways to configure
|
||||
your application so that yourapplication.fcgi does not appear in the URL.
|
||||
A preferable way is to use the ScriptAlias and SetHandler configuration
|
||||
directives to route requests to the FastCGI server. The following example
|
||||
uses FastCgiServer to start 5 instances of the application which will
|
||||
handle all incoming requests::
|
||||
|
||||
LoadModule fastcgi_module /usr/lib64/httpd/modules/mod_fastcgi.so
|
||||
|
||||
FastCgiServer /var/www/html/yourapplication/app.fcgi -idle-timeout 300 -processes 5
|
||||
|
||||
<VirtualHost *>
|
||||
ServerName webapp1.mydomain.com
|
||||
DocumentRoot /var/www/html/yourapplication
|
||||
|
||||
AddHandler fastcgi-script fcgi
|
||||
ScriptAlias / /var/www/html/yourapplication/app.fcgi/
|
||||
|
||||
<Location />
|
||||
SetHandler fastcgi-script
|
||||
</Location>
|
||||
</VirtualHost>
|
||||
|
||||
These processes will be managed by Apache. If you're using a standalone
|
||||
FastCGI server, you can use the FastCgiExternalServer directive instead.
|
||||
Note that in the following the path is not real, it's simply used as an
|
||||
identifier to other
|
||||
directives such as AliasMatch::
|
||||
|
||||
FastCgiServer /var/www/html/yourapplication -host 127.0.0.1:3000
|
||||
|
||||
If you cannot set ScriptAlias, for example on a shared web host, you can use
|
||||
WSGI middleware to remove yourapplication.fcgi from the URLs. Set .htaccess::
|
||||
|
||||
<IfModule mod_fcgid.c>
|
||||
AddHandler fcgid-script .fcgi
|
||||
<Files ~ (\.fcgi)>
|
||||
SetHandler fcgid-script
|
||||
Options +FollowSymLinks +ExecCGI
|
||||
</Files>
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
Options +FollowSymlinks
|
||||
RewriteEngine On
|
||||
RewriteBase /
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^(.*)$ yourapplication.fcgi/$1 [QSA,L]
|
||||
</IfModule>
|
||||
|
||||
Set yourapplication.fcgi::
|
||||
|
||||
#!/usr/bin/python
|
||||
#: optional path to your local python site-packages folder
|
||||
import sys
|
||||
sys.path.insert(0, '<your_local_path>/lib/python<your_python_version>/site-packages')
|
||||
|
||||
from flup.server.fcgi import WSGIServer
|
||||
from yourapplication import app
|
||||
|
||||
class ScriptNameStripper(object):
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
environ['SCRIPT_NAME'] = ''
|
||||
return self.app(environ, start_response)
|
||||
|
||||
app = ScriptNameStripper(app)
|
||||
|
||||
if __name__ == '__main__':
|
||||
WSGIServer(app).run()
|
||||
|
||||
Configuring lighttpd
|
||||
--------------------
|
||||
|
||||
A basic FastCGI configuration for lighttpd looks like that::
|
||||
|
||||
fastcgi.server = ("/yourapplication.fcgi" =>
|
||||
((
|
||||
"socket" => "/tmp/yourapplication-fcgi.sock",
|
||||
"bin-path" => "/var/www/yourapplication/yourapplication.fcgi",
|
||||
"check-local" => "disable",
|
||||
"max-procs" => 1
|
||||
))
|
||||
)
|
||||
|
||||
alias.url = (
|
||||
"/static/" => "/path/to/your/static/"
|
||||
)
|
||||
|
||||
url.rewrite-once = (
|
||||
"^(/static($|/.*))$" => "$1",
|
||||
"^(/.*)$" => "/yourapplication.fcgi$1"
|
||||
)
|
||||
|
||||
Remember to enable the FastCGI, alias and rewrite modules. This configuration
|
||||
binds the application to ``/yourapplication``. If you want the application to
|
||||
work in the URL root you have to work around a lighttpd bug with the
|
||||
:class:`~werkzeug.contrib.fixers.LighttpdCGIRootFix` middleware.
|
||||
|
||||
Make sure to apply it only if you are mounting the application the URL
|
||||
root. Also, see the Lighty docs for more information on `FastCGI and Python
|
||||
<https://redmine.lighttpd.net/projects/lighttpd/wiki/Docs_ModFastCGI>`_ (note that
|
||||
explicitly passing a socket to run() is no longer necessary).
|
||||
|
||||
Configuring nginx
|
||||
-----------------
|
||||
|
||||
Installing FastCGI applications on nginx is a bit different because by
|
||||
default no FastCGI parameters are forwarded.
|
||||
|
||||
A basic Flask FastCGI configuration for nginx looks like this::
|
||||
|
||||
location = /yourapplication { rewrite ^ /yourapplication/ last; }
|
||||
location /yourapplication { try_files $uri @yourapplication; }
|
||||
location @yourapplication {
|
||||
include fastcgi_params;
|
||||
fastcgi_split_path_info ^(/yourapplication)(.*)$;
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
|
||||
fastcgi_pass unix:/tmp/yourapplication-fcgi.sock;
|
||||
}
|
||||
|
||||
This configuration binds the application to ``/yourapplication``. If you
|
||||
want to have it in the URL root it's a bit simpler because you don't
|
||||
have to figure out how to calculate ``PATH_INFO`` and ``SCRIPT_NAME``::
|
||||
|
||||
location / { try_files $uri @yourapplication; }
|
||||
location @yourapplication {
|
||||
include fastcgi_params;
|
||||
fastcgi_param PATH_INFO $fastcgi_script_name;
|
||||
fastcgi_param SCRIPT_NAME "";
|
||||
fastcgi_pass unix:/tmp/yourapplication-fcgi.sock;
|
||||
}
|
||||
|
||||
Running FastCGI Processes
|
||||
-------------------------
|
||||
|
||||
Since nginx and others do not load FastCGI apps, you have to do it by
|
||||
yourself. `Supervisor can manage FastCGI processes.
|
||||
<http://supervisord.org/configuration.html#fcgi-program-x-section-settings>`_
|
||||
You can look around for other FastCGI process managers or write a script
|
||||
to run your `.fcgi` file at boot, e.g. using a SysV ``init.d`` script.
|
||||
For a temporary solution, you can always run the ``.fcgi`` script inside
|
||||
GNU screen. See ``man screen`` for details, and note that this is a
|
||||
manual solution which does not persist across system restart::
|
||||
|
||||
$ screen
|
||||
$ /var/www/yourapplication/yourapplication.fcgi
|
||||
|
||||
Debugging
|
||||
---------
|
||||
|
||||
FastCGI deployments tend to be hard to debug on most web servers. Very
|
||||
often the only thing the server log tells you is something along the
|
||||
lines of "premature end of headers". In order to debug the application
|
||||
the only thing that can really give you ideas why it breaks is switching
|
||||
to the correct user and executing the application by hand.
|
||||
|
||||
This example assumes your application is called `application.fcgi` and
|
||||
that your web server user is `www-data`::
|
||||
|
||||
$ su www-data
|
||||
$ cd /var/www/yourapplication
|
||||
$ python application.fcgi
|
||||
Traceback (most recent call last):
|
||||
File "yourapplication.fcgi", line 4, in <module>
|
||||
ImportError: No module named yourapplication
|
||||
|
||||
In this case the error seems to be "yourapplication" not being on the
|
||||
python path. Common problems are:
|
||||
|
||||
- Relative paths being used. Don't rely on the current working directory.
|
||||
- The code depending on environment variables that are not set by the
|
||||
web server.
|
||||
- Different python interpreters being used.
|
||||
|
||||
.. _nginx: https://nginx.org/
|
||||
.. _lighttpd: https://www.lighttpd.net/
|
||||
.. _cherokee: https://cherokee-project.com/
|
||||
.. _flup: https://pypi.org/project/flup/
|
||||
80
docs/deploying/gevent.rst
Normal file
80
docs/deploying/gevent.rst
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
gevent
|
||||
======
|
||||
|
||||
Prefer using :doc:`gunicorn` or :doc:`uwsgi` with gevent workers rather
|
||||
than using `gevent`_ directly. Gunicorn and uWSGI provide much more
|
||||
configurable and production-tested servers.
|
||||
|
||||
`gevent`_ allows writing asynchronous, coroutine-based code that looks
|
||||
like standard synchronous Python. It uses `greenlet`_ to enable task
|
||||
switching without writing ``async/await`` or using ``asyncio``.
|
||||
|
||||
:doc:`eventlet` is another library that does the same thing. Certain
|
||||
dependencies you have, or other considerations, may affect which of the
|
||||
two you choose to use.
|
||||
|
||||
gevent provides a WSGI server that can handle many connections at once
|
||||
instead of one per worker process. You must actually use gevent in your
|
||||
own code to see any benefit to using the server.
|
||||
|
||||
.. _gevent: https://www.gevent.org/
|
||||
.. _greenlet: https://greenlet.readthedocs.io/en/latest/
|
||||
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
When using gevent, greenlet>=1.0 is required, otherwise context locals
|
||||
such as ``request`` will not work as expected. When using PyPy,
|
||||
PyPy>=7.3.7 is required.
|
||||
|
||||
Create a virtualenv, install your application, then install ``gevent``.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ cd hello-app
|
||||
$ python -m venv venv
|
||||
$ . venv/bin/activate
|
||||
$ pip install . # install your application
|
||||
$ pip install gevent
|
||||
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
To use gevent to serve your application, write a script that imports its
|
||||
``WSGIServer``, as well as your app or app factory.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``wsgi.py``
|
||||
|
||||
from gevent.pywsgi import WSGIServer
|
||||
from hello import create_app
|
||||
|
||||
app = create_app()
|
||||
http_server = WSGIServer(("127.0.0.1", 8000), app)
|
||||
http_server.serve_forever()
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ python wsgi.py
|
||||
|
||||
No output is shown when the server starts.
|
||||
|
||||
|
||||
Binding Externally
|
||||
------------------
|
||||
|
||||
gevent should not be run as root because it would cause your
|
||||
application code to run as root, which is not secure. However, this
|
||||
means it will not be possible to bind to port 80 or 443. Instead, a
|
||||
reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used
|
||||
in front of gevent.
|
||||
|
||||
You can bind to all external IPs on a non-privileged port by using
|
||||
``0.0.0.0`` in the server arguments shown in the previous section. Don't
|
||||
do this when using a reverse proxy setup, otherwise it will be possible
|
||||
to bypass the proxy.
|
||||
|
||||
``0.0.0.0`` is not a valid address to navigate to, you'd use a specific
|
||||
IP address in your browser.
|
||||
130
docs/deploying/gunicorn.rst
Normal file
130
docs/deploying/gunicorn.rst
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
Gunicorn
|
||||
========
|
||||
|
||||
`Gunicorn`_ is a pure Python WSGI server with simple configuration and
|
||||
multiple worker implementations for performance tuning.
|
||||
|
||||
* It tends to integrate easily with hosting platforms.
|
||||
* It does not support Windows (but does run on WSL).
|
||||
* It is easy to install as it does not require additional dependencies
|
||||
or compilation.
|
||||
* It has built-in async worker support using gevent or eventlet.
|
||||
|
||||
This page outlines the basics of running Gunicorn. Be sure to read its
|
||||
`documentation`_ and use ``gunicorn --help`` to understand what features
|
||||
are available.
|
||||
|
||||
.. _Gunicorn: https://gunicorn.org/
|
||||
.. _documentation: https://docs.gunicorn.org/
|
||||
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
Gunicorn is easy to install, as it does not require external
|
||||
dependencies or compilation. It runs on Windows only under WSL.
|
||||
|
||||
Create a virtualenv, install your application, then install
|
||||
``gunicorn``.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ cd hello-app
|
||||
$ python -m venv venv
|
||||
$ . venv/bin/activate
|
||||
$ pip install . # install your application
|
||||
$ pip install gunicorn
|
||||
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
The only required argument to Gunicorn tells it how to load your Flask
|
||||
application. The syntax is ``{module_import}:{app_variable}``.
|
||||
``module_import`` is the dotted import name to the module with your
|
||||
application. ``app_variable`` is the variable with the application. It
|
||||
can also be a function call (with any arguments) if you're using the
|
||||
app factory pattern.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
# equivalent to 'from hello import app'
|
||||
$ gunicorn -w 4 'hello:app'
|
||||
|
||||
# equivalent to 'from hello import create_app; create_app()'
|
||||
$ gunicorn -w 4 'hello:create_app()'
|
||||
|
||||
Starting gunicorn 20.1.0
|
||||
Listening at: http://127.0.0.1:8000 (x)
|
||||
Using worker: sync
|
||||
Booting worker with pid: x
|
||||
Booting worker with pid: x
|
||||
Booting worker with pid: x
|
||||
Booting worker with pid: x
|
||||
|
||||
The ``-w`` option specifies the number of processes to run; a starting
|
||||
value could be ``CPU * 2``. The default is only 1 worker, which is
|
||||
probably not what you want for the default worker type.
|
||||
|
||||
Logs for each request aren't shown by default, only worker info and
|
||||
errors are shown. To show access logs on stdout, use the
|
||||
``--access-logfile=-`` option.
|
||||
|
||||
|
||||
Binding Externally
|
||||
------------------
|
||||
|
||||
Gunicorn should not be run as root because it would cause your
|
||||
application code to run as root, which is not secure. However, this
|
||||
means it will not be possible to bind to port 80 or 443. Instead, a
|
||||
reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used
|
||||
in front of Gunicorn.
|
||||
|
||||
You can bind to all external IPs on a non-privileged port using the
|
||||
``-b 0.0.0.0`` option. Don't do this when using a reverse proxy setup,
|
||||
otherwise it will be possible to bypass the proxy.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ gunicorn -w 4 -b 0.0.0.0 'hello:create_app()'
|
||||
Listening at: http://0.0.0.0:8000 (x)
|
||||
|
||||
``0.0.0.0`` is not a valid address to navigate to, you'd use a specific
|
||||
IP address in your browser.
|
||||
|
||||
|
||||
Async with gevent or eventlet
|
||||
-----------------------------
|
||||
|
||||
The default sync worker is appropriate for many use cases. If you need
|
||||
asynchronous support, Gunicorn provides workers using either `gevent`_
|
||||
or `eventlet`_. This is not the same as Python's ``async/await``, or the
|
||||
ASGI server spec. You must actually use gevent/eventlet in your own code
|
||||
to see any benefit to using the workers.
|
||||
|
||||
When using either gevent or eventlet, greenlet>=1.0 is required,
|
||||
otherwise context locals such as ``request`` will not work as expected.
|
||||
When using PyPy, PyPy>=7.3.7 is required.
|
||||
|
||||
To use gevent:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ gunicorn -k gevent 'hello:create_app()'
|
||||
Starting gunicorn 20.1.0
|
||||
Listening at: http://127.0.0.1:8000 (x)
|
||||
Using worker: gevent
|
||||
Booting worker with pid: x
|
||||
|
||||
To use eventlet:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ gunicorn -k eventlet 'hello:create_app()'
|
||||
Starting gunicorn 20.1.0
|
||||
Listening at: http://127.0.0.1:8000 (x)
|
||||
Using worker: eventlet
|
||||
Booting worker with pid: x
|
||||
|
||||
.. _gevent: https://www.gevent.org/
|
||||
.. _eventlet: https://eventlet.net/
|
||||
|
|
@ -1,35 +1,80 @@
|
|||
Deployment Options
|
||||
==================
|
||||
Deploying to Production
|
||||
=======================
|
||||
|
||||
While lightweight and easy to use, **Flask's built-in server is not suitable
|
||||
for production** as it doesn't scale well. Some of the options available for
|
||||
properly running Flask in production are documented here.
|
||||
After developing your application, you'll want to make it available
|
||||
publicly to other users. When you're developing locally, you're probably
|
||||
using the built-in development server, debugger, and reloader. These
|
||||
should not be used in production. Instead, you should use a dedicated
|
||||
WSGI server or hosting platform, some of which will be described here.
|
||||
|
||||
If you want to deploy your Flask application to a WSGI server not listed here,
|
||||
look up the server documentation about how to use a WSGI app with it. Just
|
||||
remember that your :class:`Flask` application object is the actual WSGI
|
||||
application.
|
||||
"Production" means "not development", which applies whether you're
|
||||
serving your application publicly to millions of users or privately /
|
||||
locally to a single user. **Do not use the development server when
|
||||
deploying to production. It is intended for use only during local
|
||||
development. It is not designed to be particularly secure, stable, or
|
||||
efficient.**
|
||||
|
||||
|
||||
Hosted options
|
||||
--------------
|
||||
|
||||
- `Deploying Flask on Heroku <https://devcenter.heroku.com/articles/getting-started-with-python>`_
|
||||
- `Deploying Flask on Google App Engine <https://cloud.google.com/appengine/docs/standard/python3/runtime>`_
|
||||
- `Deploying Flask on Google Cloud Run <https://cloud.google.com/run/docs/quickstarts/build-and-deploy/python>`_
|
||||
- `Deploying Flask on AWS Elastic Beanstalk <https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create-deploy-python-flask.html>`_
|
||||
- `Deploying on Azure (IIS) <https://docs.microsoft.com/en-us/azure/app-service/containers/how-to-configure-python>`_
|
||||
- `Deploying on PythonAnywhere <https://help.pythonanywhere.com/pages/Flask/>`_
|
||||
|
||||
Self-hosted options
|
||||
Self-Hosted Options
|
||||
-------------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
Flask is a WSGI *application*. A WSGI *server* is used to run the
|
||||
application, converting incoming HTTP requests to the standard WSGI
|
||||
environ, and converting outgoing WSGI responses to HTTP responses.
|
||||
|
||||
wsgi-standalone
|
||||
uwsgi
|
||||
mod_wsgi
|
||||
fastcgi
|
||||
cgi
|
||||
asgi
|
||||
The primary goal of these docs is to familiarize you with the concepts
|
||||
involved in running a WSGI application using a production WSGI server
|
||||
and HTTP server. There are many WSGI servers and HTTP servers, with many
|
||||
configuration possibilities. The pages below discuss the most common
|
||||
servers, and show the basics of running each one. The next section
|
||||
discusses platforms that can manage this for you.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
gunicorn
|
||||
waitress
|
||||
mod_wsgi
|
||||
uwsgi
|
||||
gevent
|
||||
eventlet
|
||||
asgi
|
||||
|
||||
WSGI servers have HTTP servers built-in. However, a dedicated HTTP
|
||||
server may be safer, more efficient, or more capable. Putting an HTTP
|
||||
server in front of the WSGI server is called a "reverse proxy."
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
proxy_fix
|
||||
nginx
|
||||
apache-httpd
|
||||
|
||||
This list is not exhaustive, and you should evaluate these and other
|
||||
servers based on your application's needs. Different servers will have
|
||||
different capabilities, configuration, and support.
|
||||
|
||||
|
||||
Hosting Platforms
|
||||
-----------------
|
||||
|
||||
There are many services available for hosting web applications without
|
||||
needing to maintain your own server, networking, domain, etc. Some
|
||||
services may have a free tier up to a certain time or bandwidth. Many of
|
||||
these services use one of the WSGI servers described above, or a similar
|
||||
interface. The links below are for some of the most common platforms,
|
||||
which have instructions for Flask, WSGI, or Python.
|
||||
|
||||
- `PythonAnywhere <https://help.pythonanywhere.com/pages/Flask/>`_
|
||||
- `Heroku <https://devcenter.heroku.com/articles/getting-started-with-python>`_
|
||||
- `Google App Engine <https://cloud.google.com/appengine/docs/standard/python3/building-app>`_
|
||||
- `Google Cloud Run <https://cloud.google.com/run/docs/quickstarts/build-and-deploy/deploy-python-service>`_
|
||||
- `AWS Elastic Beanstalk <https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create-deploy-python-flask.html>`_
|
||||
- `Microsoft Azure <https://docs.microsoft.com/en-us/azure/app-service/quickstart-python>`_
|
||||
|
||||
This list is not exhaustive, and you should evaluate these and other
|
||||
services based on your application's needs. Different services will have
|
||||
different capabilities, configuration, pricing, and support.
|
||||
|
||||
You'll probably need to :doc:`proxy_fix` when using most hosting
|
||||
platforms.
|
||||
|
|
|
|||
|
|
@ -1,216 +1,105 @@
|
|||
mod_wsgi (Apache)
|
||||
=================
|
||||
mod_wsgi
|
||||
========
|
||||
|
||||
If you are using the `Apache`_ webserver, consider using `mod_wsgi`_.
|
||||
`mod_wsgi`_ is a WSGI server integrated with the `Apache httpd`_ server.
|
||||
The modern `mod_wsgi-express`_ command makes it easy to configure and
|
||||
start the server without needing to write Apache httpd configuration.
|
||||
|
||||
.. admonition:: Watch Out
|
||||
* Tightly integrated with Apache httpd.
|
||||
* Supports Windows directly.
|
||||
* Requires a compiler to install. Requires Apache installed separately
|
||||
on Windows.
|
||||
* Does not require a reverse proxy setup.
|
||||
|
||||
Please make sure in advance that any ``app.run()`` calls you might
|
||||
have in your application file are inside an ``if __name__ ==
|
||||
'__main__':`` block or moved to a separate file. Just make sure it's
|
||||
not called because this will always start a local WSGI server which
|
||||
we do not want if we deploy that application to mod_wsgi.
|
||||
This page outlines the basics of running mod_wsgi-express, not the more
|
||||
complex installation and configuration with httpd. Be sure to read the
|
||||
`mod_wsgi-express`_, `mod_wsgi`_, and `Apache httpd`_ documentation to
|
||||
understand what features are available.
|
||||
|
||||
.. _Apache: https://httpd.apache.org/
|
||||
.. _mod_wsgi-express: https://pypi.org/project/mod-wsgi/
|
||||
.. _mod_wsgi: https://modwsgi.readthedocs.io/
|
||||
.. _Apache httpd: https://httpd.apache.org/
|
||||
|
||||
Installing `mod_wsgi`
|
||||
---------------------
|
||||
|
||||
If you don't have `mod_wsgi` installed yet you have to either install it
|
||||
using a package manager or compile it yourself. The mod_wsgi
|
||||
`installation instructions`_ cover source installations on UNIX systems.
|
||||
Installing
|
||||
----------
|
||||
|
||||
If you are using Ubuntu/Debian you can apt-get it and activate it as
|
||||
follows:
|
||||
On Linux/Mac, the most straightforward way to install mod_wsgi is to
|
||||
install the ``mod_wsgi-standalone`` package, which will compile an
|
||||
up-to-date version of Apache httpd as well.
|
||||
|
||||
.. sourcecode:: text
|
||||
Create a virtualenv, install your application, then install
|
||||
``mod_wsgi-standalone``.
|
||||
|
||||
$ apt-get install libapache2-mod-wsgi-py3
|
||||
.. code-block:: text
|
||||
|
||||
If you are using a yum based distribution (Fedora, OpenSUSE, etc..) you
|
||||
can install it as follows:
|
||||
$ cd hello-app
|
||||
$ python -m venv venv
|
||||
$ . venv/bin/activate
|
||||
$ pip install . # install your application
|
||||
$ pip install mod_wsgi-standalone
|
||||
|
||||
.. sourcecode:: text
|
||||
If you want to use the system-installed version of Apache httpd
|
||||
(required on Windows, optional but faster on Linux/Mac), install the
|
||||
``mod_wsgi`` package instead. You will get an error if Apache and its
|
||||
development headers are not available. How to install them depends on
|
||||
what OS and package manager you use.
|
||||
|
||||
$ yum install mod_wsgi
|
||||
.. code-block:: text
|
||||
|
||||
On FreeBSD install `mod_wsgi` by compiling the `www/mod_wsgi` port or by
|
||||
using pkg_add:
|
||||
$ pip install mod_wsgi
|
||||
|
||||
.. sourcecode:: text
|
||||
|
||||
$ pkg install ap24-py37-mod_wsgi
|
||||
Running
|
||||
-------
|
||||
|
||||
If you are using pkgsrc you can install `mod_wsgi` by compiling the
|
||||
`www/ap2-wsgi` package.
|
||||
The only argument to ``mod_wsgi-express`` specifies a script containing
|
||||
your Flask application, which must be called ``application``. You can
|
||||
write a small script to import your app with this name, or to create it
|
||||
if using the app factory pattern.
|
||||
|
||||
If you encounter segfaulting child processes after the first apache
|
||||
reload you can safely ignore them. Just restart the server.
|
||||
.. code-block:: python
|
||||
:caption: ``wsgi.py``
|
||||
|
||||
Creating a `.wsgi` file
|
||||
-----------------------
|
||||
from hello import app
|
||||
|
||||
To run your application you need a :file:`yourapplication.wsgi` file.
|
||||
This file contains the code `mod_wsgi` is executing on startup
|
||||
to get the application object. The object called `application`
|
||||
in that file is then used as application.
|
||||
application = app
|
||||
|
||||
For most applications the following file should be sufficient::
|
||||
.. code-block:: python
|
||||
:caption: ``wsgi.py``
|
||||
|
||||
from yourapplication import app as application
|
||||
from hello import create_app
|
||||
|
||||
If a factory function is used in a :file:`__init__.py` file, then the function should be imported::
|
||||
|
||||
from yourapplication import create_app
|
||||
application = create_app()
|
||||
|
||||
If you don't have a factory function for application creation but a singleton
|
||||
instance you can directly import that one as `application`.
|
||||
Now run the ``mod_wsgi-express start-server`` command.
|
||||
|
||||
Store that file somewhere that you will find it again (e.g.:
|
||||
:file:`/var/www/yourapplication`) and make sure that `yourapplication` and all
|
||||
the libraries that are in use are on the python load path. If you don't
|
||||
want to install it system wide consider using a `virtual python`_
|
||||
instance. Keep in mind that you will have to actually install your
|
||||
application into the virtualenv as well. Alternatively there is the
|
||||
option to just patch the path in the ``.wsgi`` file before the import::
|
||||
.. code-block:: text
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, '/path/to/the/application')
|
||||
$ mod_wsgi-express start-server wsgi.py --processes 4
|
||||
|
||||
Configuring Apache
|
||||
The ``--processes`` option specifies the number of worker processes to
|
||||
run; a starting value could be ``CPU * 2``.
|
||||
|
||||
Logs for each request aren't show in the terminal. If an error occurs,
|
||||
its information is written to the error log file shown when starting the
|
||||
server.
|
||||
|
||||
|
||||
Binding Externally
|
||||
------------------
|
||||
|
||||
The last thing you have to do is to create an Apache configuration file
|
||||
for your application. In this example we are telling `mod_wsgi` to
|
||||
execute the application under a different user for security reasons:
|
||||
Unlike the other WSGI servers in these docs, mod_wsgi can be run as
|
||||
root to bind to privileged ports like 80 and 443. However, it must be
|
||||
configured to drop permissions to a different user and group for the
|
||||
worker processes.
|
||||
|
||||
.. sourcecode:: apache
|
||||
For example, if you created a ``hello`` user and group, you should
|
||||
install your virtualenv and application as that user, then tell
|
||||
mod_wsgi to drop to that user after starting.
|
||||
|
||||
<VirtualHost *>
|
||||
ServerName example.com
|
||||
.. code-block:: text
|
||||
|
||||
WSGIDaemonProcess yourapplication user=user1 group=group1 threads=5
|
||||
WSGIScriptAlias / /var/www/yourapplication/yourapplication.wsgi
|
||||
|
||||
<Directory /var/www/yourapplication>
|
||||
WSGIProcessGroup yourapplication
|
||||
WSGIApplicationGroup %{GLOBAL}
|
||||
Order deny,allow
|
||||
Allow from all
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
|
||||
Note: WSGIDaemonProcess isn't implemented in Windows and Apache will
|
||||
refuse to run with the above configuration. On a Windows system, eliminate those lines:
|
||||
|
||||
.. sourcecode:: apache
|
||||
|
||||
<VirtualHost *>
|
||||
ServerName example.com
|
||||
WSGIScriptAlias / C:\yourdir\yourapp.wsgi
|
||||
<Directory C:\yourdir>
|
||||
Order deny,allow
|
||||
Allow from all
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
|
||||
Note: There have been some changes in access control configuration
|
||||
for `Apache 2.4`_.
|
||||
|
||||
.. _Apache 2.4: https://httpd.apache.org/docs/trunk/upgrading.html
|
||||
|
||||
Most notably, the syntax for directory permissions has changed from httpd 2.2
|
||||
|
||||
.. sourcecode:: apache
|
||||
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
|
||||
to httpd 2.4 syntax
|
||||
|
||||
.. sourcecode:: apache
|
||||
|
||||
Require all granted
|
||||
|
||||
|
||||
For more information consult the `mod_wsgi documentation`_.
|
||||
|
||||
.. _mod_wsgi: https://github.com/GrahamDumpleton/mod_wsgi
|
||||
.. _installation instructions: https://modwsgi.readthedocs.io/en/develop/installation.html
|
||||
.. _virtual python: https://pypi.org/project/virtualenv/
|
||||
.. _mod_wsgi documentation: https://modwsgi.readthedocs.io/en/develop/index.html
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
|
||||
If your application does not run, follow this guide to troubleshoot:
|
||||
|
||||
**Problem:** application does not run, errorlog shows SystemExit ignored
|
||||
You have an ``app.run()`` call in your application file that is not
|
||||
guarded by an ``if __name__ == '__main__':`` condition. Either
|
||||
remove that :meth:`~flask.Flask.run` call from the file and move it
|
||||
into a separate :file:`run.py` file or put it into such an if block.
|
||||
|
||||
**Problem:** application gives permission errors
|
||||
Probably caused by your application running as the wrong user. Make
|
||||
sure the folders the application needs access to have the proper
|
||||
privileges set and the application runs as the correct user
|
||||
(``user`` and ``group`` parameter to the `WSGIDaemonProcess`
|
||||
directive)
|
||||
|
||||
**Problem:** application dies with an error on print
|
||||
Keep in mind that mod_wsgi disallows doing anything with
|
||||
:data:`sys.stdout` and :data:`sys.stderr`. You can disable this
|
||||
protection from the config by setting the `WSGIRestrictStdout` to
|
||||
``off``:
|
||||
|
||||
.. sourcecode:: apache
|
||||
|
||||
WSGIRestrictStdout Off
|
||||
|
||||
Alternatively you can also replace the standard out in the .wsgi file
|
||||
with a different stream::
|
||||
|
||||
import sys
|
||||
sys.stdout = sys.stderr
|
||||
|
||||
**Problem:** accessing resources gives IO errors
|
||||
Your application probably is a single .py file you symlinked into
|
||||
the site-packages folder. Please be aware that this does not work,
|
||||
instead you either have to put the folder into the pythonpath the
|
||||
file is stored in, or convert your application into a package.
|
||||
|
||||
The reason for this is that for non-installed packages, the module
|
||||
filename is used to locate the resources and for symlinks the wrong
|
||||
filename is picked up.
|
||||
|
||||
Support for Automatic Reloading
|
||||
-------------------------------
|
||||
|
||||
To help deployment tools you can activate support for automatic
|
||||
reloading. Whenever something changes the ``.wsgi`` file, `mod_wsgi` will
|
||||
reload all the daemon processes for us.
|
||||
|
||||
For that, just add the following directive to your `Directory` section:
|
||||
|
||||
.. sourcecode:: apache
|
||||
|
||||
WSGIScriptReloading On
|
||||
|
||||
Working with Virtual Environments
|
||||
---------------------------------
|
||||
|
||||
Virtual environments have the advantage that they never install the
|
||||
required dependencies system wide so you have a better control over what
|
||||
is used where. If you want to use a virtual environment with mod_wsgi
|
||||
you have to modify your ``.wsgi`` file slightly.
|
||||
|
||||
Add the following lines to the top of your ``.wsgi`` file::
|
||||
|
||||
activate_this = '/path/to/env/bin/activate_this.py'
|
||||
with open(activate_this) as file_:
|
||||
exec(file_.read(), dict(__file__=activate_this))
|
||||
|
||||
This sets up the load paths according to the settings of the virtual
|
||||
environment. Keep in mind that the path has to be absolute.
|
||||
$ sudo /home/hello/venv/bin/mod_wsgi-express start-server \
|
||||
/home/hello/wsgi.py \
|
||||
--user hello --group hello --port 80 --processes 4
|
||||
|
|
|
|||
69
docs/deploying/nginx.rst
Normal file
69
docs/deploying/nginx.rst
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
nginx
|
||||
=====
|
||||
|
||||
`nginx`_ is a fast, production level HTTP server. When serving your
|
||||
application with one of the WSGI servers listed in :doc:`index`, it is
|
||||
often good or necessary to put a dedicated HTTP server in front of it.
|
||||
This "reverse proxy" can handle incoming requests, TLS, and other
|
||||
security and performance concerns better than the WSGI server.
|
||||
|
||||
Nginx can be installed using your system package manager, or a pre-built
|
||||
executable for Windows. Installing and running Nginx itself is outside
|
||||
the scope of this doc. This page outlines the basics of configuring
|
||||
Nginx to proxy your application. Be sure to read its documentation to
|
||||
understand what features are available.
|
||||
|
||||
.. _nginx: https://nginx.org/
|
||||
|
||||
|
||||
Domain Name
|
||||
-----------
|
||||
|
||||
Acquiring and configuring a domain name is outside the scope of this
|
||||
doc. In general, you will buy a domain name from a registrar, pay for
|
||||
server space with a hosting provider, and then point your registrar
|
||||
at the hosting provider's name servers.
|
||||
|
||||
To simulate this, you can also edit your ``hosts`` file, located at
|
||||
``/etc/hosts`` on Linux. Add a line that associates a name with the
|
||||
local IP.
|
||||
|
||||
Modern Linux systems may be configured to treat any domain name that
|
||||
ends with ``.localhost`` like this without adding it to the ``hosts``
|
||||
file.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``/etc/hosts``
|
||||
|
||||
127.0.0.1 hello.localhost
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
The nginx configuration is located at ``/etc/nginx/nginx.conf`` on
|
||||
Linux. It may be different depending on your operating system. Check the
|
||||
docs and look for ``nginx.conf``.
|
||||
|
||||
Remove or comment out any existing ``server`` section. Add a ``server``
|
||||
section and use the ``proxy_pass`` directive to point to the address the
|
||||
WSGI server is listening on. We'll assume the WSGI server is listening
|
||||
locally at ``http://127.0.0.1:8000``.
|
||||
|
||||
.. code-block:: nginx
|
||||
:caption: ``/etc/nginx.conf``
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8000/;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Prefix /;
|
||||
}
|
||||
}
|
||||
|
||||
Then :doc:`proxy_fix` so that your application uses these headers.
|
||||
33
docs/deploying/proxy_fix.rst
Normal file
33
docs/deploying/proxy_fix.rst
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
Tell Flask it is Behind a Proxy
|
||||
===============================
|
||||
|
||||
When using a reverse proxy, or many Python hosting platforms, the proxy
|
||||
will intercept and forward all external requests to the local WSGI
|
||||
server.
|
||||
|
||||
From the WSGI server and Flask application's perspectives, requests are
|
||||
now coming from the HTTP server to the local address, rather than from
|
||||
the remote address to the external server address.
|
||||
|
||||
HTTP servers should set ``X-Forwarded-`` headers to pass on the real
|
||||
values to the application. The application can then be told to trust and
|
||||
use those values by wrapping it with the
|
||||
:doc:`werkzeug:middleware/proxy_fix` middleware provided by Werkzeug.
|
||||
|
||||
This middleware should only be used if the application is actually
|
||||
behind a proxy, and should be configured with the number of proxies that
|
||||
are chained in front of it. Not all proxies set all the headers. Since
|
||||
incoming headers can be faked, you must set how many proxies are setting
|
||||
each header so the middleware knows what to trust.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
|
||||
app.wsgi_app = ProxyFix(
|
||||
app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1
|
||||
)
|
||||
|
||||
Remember, only apply this middleware if you are behind a proxy, and set
|
||||
the correct number of proxies that set each header. It can be a security
|
||||
issue if you get this configuration wrong.
|
||||
|
|
@ -1,71 +1,145 @@
|
|||
uWSGI
|
||||
=====
|
||||
|
||||
uWSGI is a deployment option on servers like `nginx`_, `lighttpd`_, and
|
||||
`cherokee`_; see :doc:`fastcgi` and :doc:`wsgi-standalone` for other options.
|
||||
To use your WSGI application with uWSGI protocol you will need a uWSGI server
|
||||
first. uWSGI is both a protocol and an application server; the application
|
||||
server can serve uWSGI, FastCGI, and HTTP protocols.
|
||||
`uWSGI`_ is a fast, compiled server suite with extensive configuration
|
||||
and capabilities beyond a basic server.
|
||||
|
||||
The most popular uWSGI server is `uwsgi`_, which we will use for this
|
||||
guide. Make sure to have it installed to follow along.
|
||||
* It can be very performant due to being a compiled program.
|
||||
* It is complex to configure beyond the basic application, and has so
|
||||
many options that it can be difficult for beginners to understand.
|
||||
* It does not support Windows (but does run on WSL).
|
||||
* It requires a compiler to install in some cases.
|
||||
|
||||
.. admonition:: Watch Out
|
||||
This page outlines the basics of running uWSGI. Be sure to read its
|
||||
documentation to understand what features are available.
|
||||
|
||||
Please make sure in advance that any ``app.run()`` calls you might
|
||||
have in your application file are inside an ``if __name__ ==
|
||||
'__main__':`` block or moved to a separate file. Just make sure it's
|
||||
not called because this will always start a local WSGI server which
|
||||
we do not want if we deploy that application to uWSGI.
|
||||
.. _uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/
|
||||
|
||||
Starting your app with uwsgi
|
||||
----------------------------
|
||||
|
||||
`uwsgi` is designed to operate on WSGI callables found in python modules.
|
||||
Installing
|
||||
----------
|
||||
|
||||
Given a flask application in myapp.py, use the following command:
|
||||
uWSGI has multiple ways to install it. The most straightforward is to
|
||||
install the ``pyuwsgi`` package, which provides precompiled wheels for
|
||||
common platforms. However, it does not provide SSL support, which can be
|
||||
provided with a reverse proxy instead.
|
||||
|
||||
.. sourcecode:: text
|
||||
Create a virtualenv, install your application, then install ``pyuwsgi``.
|
||||
|
||||
$ uwsgi -s /tmp/yourapplication.sock --manage-script-name --mount /yourapplication=myapp:app
|
||||
.. code-block:: text
|
||||
|
||||
The ``--manage-script-name`` will move the handling of ``SCRIPT_NAME``
|
||||
to uwsgi, since it is smarter about that.
|
||||
It is used together with the ``--mount`` directive which will make
|
||||
requests to ``/yourapplication`` be directed to ``myapp:app``.
|
||||
If your application is accessible at root level, you can use a
|
||||
single ``/`` instead of ``/yourapplication``. ``myapp`` refers to the name of
|
||||
the file of your flask application (without extension) or the module which
|
||||
provides ``app``. ``app`` is the callable inside of your application (usually
|
||||
the line reads ``app = Flask(__name__)``).
|
||||
$ cd hello-app
|
||||
$ python -m venv venv
|
||||
$ . venv/bin/activate
|
||||
$ pip install . # install your application
|
||||
$ pip install pyuwsgi
|
||||
|
||||
If you want to deploy your flask application inside of a virtual environment,
|
||||
you need to also add ``--virtualenv /path/to/virtual/environment``. You might
|
||||
also need to add ``--plugin python`` or ``--plugin python3`` depending on which
|
||||
python version you use for your project.
|
||||
If you have a compiler available, you can install the ``uwsgi`` package
|
||||
instead. Or install the ``pyuwsgi`` package from sdist instead of wheel.
|
||||
Either method will include SSL support.
|
||||
|
||||
Configuring nginx
|
||||
.. code-block:: text
|
||||
|
||||
$ pip install uwsgi
|
||||
|
||||
# or
|
||||
$ pip install --no-binary pyuwsgi pyuwsgi
|
||||
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
The most basic way to run uWSGI is to tell it to start an HTTP server
|
||||
and import your application.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ uwsgi --http 127.0.0.1:8000 --master -p 4 -w hello:app
|
||||
|
||||
*** Starting uWSGI 2.0.20 (64bit) on [x] ***
|
||||
*** Operational MODE: preforking ***
|
||||
mounting hello:app on /
|
||||
spawned uWSGI master process (pid: x)
|
||||
spawned uWSGI worker 1 (pid: x, cores: 1)
|
||||
spawned uWSGI worker 2 (pid: x, cores: 1)
|
||||
spawned uWSGI worker 3 (pid: x, cores: 1)
|
||||
spawned uWSGI worker 4 (pid: x, cores: 1)
|
||||
spawned uWSGI http 1 (pid: x)
|
||||
|
||||
If you're using the app factory pattern, you'll need to create a small
|
||||
Python file to create the app, then point uWSGI at that.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``wsgi.py``
|
||||
|
||||
from hello import create_app
|
||||
|
||||
app = create_app()
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ uwsgi --http 127.0.0.1:8000 --master -p 4 -w wsgi:app
|
||||
|
||||
The ``--http`` option starts an HTTP server at 127.0.0.1 port 8000. The
|
||||
``--master`` option specifies the standard worker manager. The ``-p``
|
||||
option starts 4 worker processes; a starting value could be ``CPU * 2``.
|
||||
The ``-w`` option tells uWSGI how to import your application
|
||||
|
||||
|
||||
Binding Externally
|
||||
------------------
|
||||
|
||||
uWSGI should not be run as root with the configuration shown in this doc
|
||||
because it would cause your application code to run as root, which is
|
||||
not secure. However, this means it will not be possible to bind to port
|
||||
80 or 443. Instead, a reverse proxy such as :doc:`nginx` or
|
||||
:doc:`apache-httpd` should be used in front of uWSGI. It is possible to
|
||||
run uWSGI as root securely, but that is beyond the scope of this doc.
|
||||
|
||||
uWSGI has optimized integration with `Nginx uWSGI`_ and
|
||||
`Apache mod_proxy_uwsgi`_, and possibly other servers, instead of using
|
||||
a standard HTTP proxy. That configuration is beyond the scope of this
|
||||
doc, see the links for more information.
|
||||
|
||||
.. _Nginx uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/Nginx.html
|
||||
.. _Apache mod_proxy_uwsgi: https://uwsgi-docs.readthedocs.io/en/latest/Apache.html#mod-proxy-uwsgi
|
||||
|
||||
You can bind to all external IPs on a non-privileged port using the
|
||||
``--http 0.0.0.0:8000`` option. Don't do this when using a reverse proxy
|
||||
setup, otherwise it will be possible to bypass the proxy.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ uwsgi --http 0.0.0.0:8000 --master -p 4 -w wsgi:app
|
||||
|
||||
``0.0.0.0`` is not a valid address to navigate to, you'd use a specific
|
||||
IP address in your browser.
|
||||
|
||||
|
||||
Async with gevent
|
||||
-----------------
|
||||
|
||||
A basic flask nginx configuration looks like this::
|
||||
The default sync worker is appropriate for many use cases. If you need
|
||||
asynchronous support, uWSGI provides a `gevent`_ worker. This is not the
|
||||
same as Python's ``async/await``, or the ASGI server spec. You must
|
||||
actually use gevent in your own code to see any benefit to using the
|
||||
worker.
|
||||
|
||||
location = /yourapplication { rewrite ^ /yourapplication/; }
|
||||
location /yourapplication { try_files $uri @yourapplication; }
|
||||
location @yourapplication {
|
||||
include uwsgi_params;
|
||||
uwsgi_pass unix:/tmp/yourapplication.sock;
|
||||
}
|
||||
When using gevent, greenlet>=1.0 is required, otherwise context locals
|
||||
such as ``request`` will not work as expected. When using PyPy,
|
||||
PyPy>=7.3.7 is required.
|
||||
|
||||
This configuration binds the application to ``/yourapplication``. If you want
|
||||
to have it in the URL root its a bit simpler::
|
||||
.. code-block:: text
|
||||
|
||||
location / { try_files $uri @yourapplication; }
|
||||
location @yourapplication {
|
||||
include uwsgi_params;
|
||||
uwsgi_pass unix:/tmp/yourapplication.sock;
|
||||
}
|
||||
$ uwsgi --http 127.0.0.1:8000 --master --gevent 100 -w wsgi:app
|
||||
|
||||
.. _nginx: https://nginx.org/
|
||||
.. _lighttpd: https://www.lighttpd.net/
|
||||
.. _cherokee: https://cherokee-project.com/
|
||||
.. _uwsgi: https://uwsgi-docs.readthedocs.io/en/latest/
|
||||
*** Starting uWSGI 2.0.20 (64bit) on [x] ***
|
||||
*** Operational MODE: async ***
|
||||
mounting hello:app on /
|
||||
spawned uWSGI master process (pid: x)
|
||||
spawned uWSGI worker 1 (pid: x, cores: 100)
|
||||
spawned uWSGI http 1 (pid: x)
|
||||
*** running gevent loop engine [addr:x] ***
|
||||
|
||||
|
||||
.. _gevent: https://www.gevent.org/
|
||||
|
|
|
|||
75
docs/deploying/waitress.rst
Normal file
75
docs/deploying/waitress.rst
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
Waitress
|
||||
========
|
||||
|
||||
`Waitress`_ is a pure Python WSGI server.
|
||||
|
||||
* It is easy to configure.
|
||||
* It supports Windows directly.
|
||||
* It is easy to install as it does not require additional dependencies
|
||||
or compilation.
|
||||
* It does not support streaming requests, full request data is always
|
||||
buffered.
|
||||
* It uses a single process with multiple thread workers.
|
||||
|
||||
This page outlines the basics of running Waitress. Be sure to read its
|
||||
documentation and ``waitress-serve --help`` to understand what features
|
||||
are available.
|
||||
|
||||
.. _Waitress: https://docs.pylonsproject.org/projects/waitress/
|
||||
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
Create a virtualenv, install your application, then install
|
||||
``waitress``.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ cd hello-app
|
||||
$ python -m venv venv
|
||||
$ . venv/bin/activate
|
||||
$ pip install . # install your application
|
||||
$ pip install waitress
|
||||
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
The only required argument to ``waitress-serve`` tells it how to load
|
||||
your Flask application. The syntax is ``{module}:{app}``. ``module`` is
|
||||
the dotted import name to the module with your application. ``app`` is
|
||||
the variable with the application. If you're using the app factory
|
||||
pattern, use ``--call {module}:{factory}`` instead.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
# equivalent to 'from hello import app'
|
||||
$ waitress-serve hello:app --host 127.0.0.1
|
||||
|
||||
# equivalent to 'from hello import create_app; create_app()'
|
||||
$ waitress-serve --call hello:create_app --host 127.0.0.1
|
||||
|
||||
Serving on http://127.0.0.1:8080
|
||||
|
||||
The ``--host`` option binds the server to local ``127.0.0.1`` only.
|
||||
|
||||
Logs for each request aren't shown, only errors are shown. Logging can
|
||||
be configured through the Python interface instead of the command line.
|
||||
|
||||
|
||||
Binding Externally
|
||||
------------------
|
||||
|
||||
Waitress should not be run as root because it would cause your
|
||||
application code to run as root, which is not secure. However, this
|
||||
means it will not be possible to bind to port 80 or 443. Instead, a
|
||||
reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used
|
||||
in front of Waitress.
|
||||
|
||||
You can bind to all external IPs on a non-privileged port by not
|
||||
specifying the ``--host`` option. Don't do this when using a revers
|
||||
proxy setup, otherwise it will be possible to bypass the proxy.
|
||||
|
||||
``0.0.0.0`` is not a valid address to navigate to, you'd use a specific
|
||||
IP address in your browser.
|
||||
|
|
@ -1,262 +0,0 @@
|
|||
Standalone WSGI Servers
|
||||
=======================
|
||||
|
||||
Most WSGI servers also provide HTTP servers, so they can run a WSGI
|
||||
application and make it available externally.
|
||||
|
||||
It may still be a good idea to run the server behind a dedicated HTTP
|
||||
server such as Apache or Nginx. See :ref:`deploying-proxy-setups` if you
|
||||
run into issues with that.
|
||||
|
||||
|
||||
Gunicorn
|
||||
--------
|
||||
|
||||
`Gunicorn`_ is a WSGI and HTTP server for UNIX. To run a Flask
|
||||
application, tell Gunicorn how to import your Flask app object.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ gunicorn -w 4 -b 0.0.0.0:5000 your_project:app
|
||||
|
||||
The ``-w 4`` option uses 4 workers to handle 4 requests at once. The
|
||||
``-b 0.0.0.0:5000`` serves the application on all interfaces on port
|
||||
5000.
|
||||
|
||||
Gunicorn provides many options for configuring the server, either
|
||||
through a configuration file or with command line options. Use
|
||||
``gunicorn --help`` or see the docs for more information.
|
||||
|
||||
The command expects the name of your module or package to import and
|
||||
the application instance within the module. If you use the application
|
||||
factory pattern, you can pass a call to that.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ gunicorn -w 4 -b 0.0.0.0:5000 "myproject:create_app()"
|
||||
|
||||
|
||||
Async with Gevent or Eventlet
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The default sync worker is appropriate for many use cases. If you need
|
||||
asynchronous support, Gunicorn provides workers using either `gevent`_
|
||||
or `eventlet`_. This is not the same as Python's ``async/await``, or the
|
||||
ASGI server spec.
|
||||
|
||||
When using either gevent or eventlet, greenlet>=1.0 is required,
|
||||
otherwise context locals such as ``request`` will not work as expected.
|
||||
When using PyPy, PyPy>=7.3.7 is required.
|
||||
|
||||
To use gevent:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ gunicorn -k gevent -b 0.0.0.0:5000 your_project:app
|
||||
|
||||
To use eventlet:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ gunicorn -k eventlet -b 0.0.0.0:5000 your_project:app
|
||||
|
||||
|
||||
.. _Gunicorn: https://gunicorn.org/
|
||||
.. _gevent: http://www.gevent.org/
|
||||
.. _eventlet: https://eventlet.net/
|
||||
.. _greenlet: https://greenlet.readthedocs.io/en/latest/
|
||||
|
||||
|
||||
uWSGI
|
||||
-----
|
||||
|
||||
`uWSGI`_ is a fast application server written in C. It is very
|
||||
configurable, which makes it more complicated to setup than Gunicorn.
|
||||
It also provides many other utilities for writing robust web
|
||||
applications. To run a Flask application, tell uWSGI how to import
|
||||
your Flask app object.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ uwsgi --master -p 4 --http 0.0.0.0:5000 -w your_project:app
|
||||
|
||||
The ``-p 4`` option uses 4 workers to handle 4 requests at once. The
|
||||
``--http 0.0.0.0:5000`` serves the application on all interfaces on port
|
||||
5000.
|
||||
|
||||
uWSGI has optimized integration with Nginx and Apache instead of using
|
||||
a standard HTTP proxy. See :doc:`configuring uWSGI and Nginx <uwsgi>`.
|
||||
|
||||
|
||||
Async with Gevent
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The default sync worker is appropriate for many use cases. If you need
|
||||
asynchronous support, uWSGI provides workers using `gevent`_. It also
|
||||
supports other async modes, see the docs for more information. This is
|
||||
not the same as Python's ``async/await``, or the ASGI server spec.
|
||||
|
||||
When using gevent, greenlet>=1.0 is required, otherwise context locals
|
||||
such as ``request`` will not work as expected. When using PyPy,
|
||||
PyPy>=7.3.7 is required.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ uwsgi --master --gevent 100 --http 0.0.0.0:5000 -w your_project:app
|
||||
|
||||
.. _uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/
|
||||
|
||||
|
||||
Gevent
|
||||
------
|
||||
|
||||
Prefer using `Gunicorn`_ with Gevent workers rather than using Gevent
|
||||
directly. Gunicorn provides a much more configurable and
|
||||
production-tested server. See the section on Gunicorn above.
|
||||
|
||||
`Gevent`_ allows writing asynchronous, coroutine-based code that looks
|
||||
like standard synchronous Python. It uses `greenlet`_ to enable task
|
||||
switching without writing ``async/await`` or using ``asyncio``.
|
||||
|
||||
It provides a WSGI server that can handle many connections at once
|
||||
instead of one per worker process.
|
||||
|
||||
`Eventlet`_, described below, is another library that does the same
|
||||
thing. Certain dependencies you have, or other consideration, may affect
|
||||
which of the two you choose to use
|
||||
|
||||
To use gevent to serve your application, import its ``WSGIServer`` and
|
||||
use it to run your ``app``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from gevent.pywsgi import WSGIServer
|
||||
from your_project import app
|
||||
|
||||
http_server = WSGIServer(("", 5000), app)
|
||||
http_server.serve_forever()
|
||||
|
||||
|
||||
Eventlet
|
||||
--------
|
||||
|
||||
Prefer using `Gunicorn`_ with Eventlet workers rather than using
|
||||
Eventlet directly. Gunicorn provides a much more configurable and
|
||||
production-tested server. See the section on Gunicorn above.
|
||||
|
||||
`Eventlet`_ allows writing asynchronous, coroutine-based code that looks
|
||||
like standard synchronous Python. It uses `greenlet`_ to enable task
|
||||
switching without writing ``async/await`` or using ``asyncio``.
|
||||
|
||||
It provides a WSGI server that can handle many connections at once
|
||||
instead of one per worker process.
|
||||
|
||||
`Gevent`_, described above, is another library that does the same
|
||||
thing. Certain dependencies you have, or other consideration, may affect
|
||||
which of the two you choose to use
|
||||
|
||||
To use eventlet to serve your application, import its ``wsgi.server``
|
||||
and use it to run your ``app``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import eventlet
|
||||
from eventlet import wsgi
|
||||
from your_project import app
|
||||
|
||||
wsgi.server(eventlet.listen(("", 5000), app)
|
||||
|
||||
|
||||
Twisted Web
|
||||
-----------
|
||||
|
||||
`Twisted Web`_ is the web server shipped with `Twisted`_, a mature,
|
||||
non-blocking event-driven networking library. Twisted Web comes with a
|
||||
standard WSGI container which can be controlled from the command line using
|
||||
the ``twistd`` utility:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ twistd web --wsgi myproject.app
|
||||
|
||||
This example will run a Flask application called ``app`` from a module named
|
||||
``myproject``.
|
||||
|
||||
Twisted Web supports many flags and options, and the ``twistd`` utility does
|
||||
as well; see ``twistd -h`` and ``twistd web -h`` for more information. For
|
||||
example, to run a Twisted Web server in the foreground, on port 8080, with an
|
||||
application from ``myproject``:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ twistd -n web --port tcp:8080 --wsgi myproject.app
|
||||
|
||||
.. _Twisted: https://twistedmatrix.com/trac/
|
||||
.. _Twisted Web: https://twistedmatrix.com/trac/wiki/TwistedWeb
|
||||
|
||||
|
||||
.. _deploying-proxy-setups:
|
||||
|
||||
Proxy Setups
|
||||
------------
|
||||
|
||||
If you deploy your application using one of these servers behind an HTTP proxy
|
||||
you will need to rewrite a few headers in order for the application to work.
|
||||
The two problematic values in the WSGI environment usually are ``REMOTE_ADDR``
|
||||
and ``HTTP_HOST``. You can configure your httpd to pass these headers, or you
|
||||
can fix them in middleware. Werkzeug ships a fixer that will solve some common
|
||||
setups, but you might want to write your own WSGI middleware for specific
|
||||
setups.
|
||||
|
||||
Here's a simple nginx configuration which proxies to an application served on
|
||||
localhost at port 8000, setting appropriate headers:
|
||||
|
||||
.. sourcecode:: nginx
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
server_name _;
|
||||
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8000/;
|
||||
proxy_redirect off;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
|
||||
If your httpd is not providing these headers, the most common setup invokes the
|
||||
host being set from ``X-Forwarded-Host`` and the remote address from
|
||||
``X-Forwarded-For``::
|
||||
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1)
|
||||
|
||||
.. admonition:: Trusting Headers
|
||||
|
||||
Please keep in mind that it is a security issue to use such a middleware in
|
||||
a non-proxy setup because it will blindly trust the incoming headers which
|
||||
might be forged by malicious clients.
|
||||
|
||||
If you want to rewrite the headers from another header, you might want to
|
||||
use a fixer like this::
|
||||
|
||||
class CustomProxyFix(object):
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
host = environ.get('HTTP_X_FHOST', '')
|
||||
if host:
|
||||
environ['HTTP_HOST'] = host
|
||||
return self.app(environ, start_response)
|
||||
|
||||
app.wsgi_app = CustomProxyFix(app.wsgi_app)
|
||||
Loading…
Add table
Add a link
Reference in a new issue