Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: zopefoundation/Zope
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 71e849ca39908ae8237e07c9e8b93c35b6a8ce83
Choose a base ref
..
head repository: zopefoundation/Zope
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 7f4c61c0d7fe0751be93e80683659271fa0c65a3
Choose a head ref
Showing with 76 additions and 206 deletions.
  1. +5 −0 CHANGES.rst
  2. +41 −56 docs/INSTALL.rst
  3. +17 −141 docs/operation.rst
  4. +7 −7 src/ZPublisher/HTTPResponse.py
  5. +6 −2 src/ZPublisher/tests/testHTTPResponse.py
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -10,9 +10,14 @@ https://github.com/zopefoundation/Zope/blob/4.x/CHANGES.rst
5.9.1 (unreleased)
------------------

- Clean up and fix installation documentation.

- Officially support Python 3.12.1.
(`#1188 <https://github.com/zopefoundation/Zope/issues/1188>`_)

- Fix redirections to URLs with host given as IP-literal with brackets.
Fixes `#1191 <https://github.com/zopefoundation/Zope/issues/1191>`_.

- Fix ``Content-Disposition`` filename for clients without rfc6266 support.
(`#1198 <https://github.com/zopefoundation/Zope/pull/1198>`_)

97 changes: 41 additions & 56 deletions docs/INSTALL.rst
Original file line number Diff line number Diff line change
@@ -33,70 +33,47 @@ available:
$ sudo apt-get install python3-dev
Choice of installation methods
------------------------------
Zope can be installed using either straight ``pip`` or the ``zc.buildout``
buildout and deployment tool.

You can use ``pip`` and the built-in script ``mkwsgiinstance`` for testing or
very simple setups that don't require much customization or scripting. It will
create a basic set of configurations and a simple start/stop script.

If you need customization and if you don't want to maintain configurations and
scripts by hand you should use ``zc.buildout`` in conjunction with the buildout
add-on ``plone.recipe.zope2instance`` instead. This is a powerful combination
for creating repeatable builds for a given configuration and environment.
The Zope developers use ``zc.buildout`` to develop Zope itself as well as the
dependency packages it uses. **This is the recommended way of installing Zope**.

Installing Zope with ``zc.buildout``
------------------------------------
`zc.buildout <https://pypi.org/project/zc.buildout/>`_ is a powerful
tool for creating repeatable builds of a given software configuration
and environment. The Zope developers use ``zc.buildout`` to develop
Zope itself, as well as the underlying packages it uses. **This is the
recommended way of installing Zope**.

Installing the Zope software using ``zc.buildout`` involves the following
steps:

- Download and uncompress the Zope source distribution from `PyPI`__ if you
are using the built-in standard buildout configuration

__ https://pypi.org/project/Zope/

- Create a virtual environment

- Install ``zc.buildout`` into the virtual environment

- Create a buildout configuration file ``buildout.cfg``
- Run the buildout

The following examples are from Linux and use Zope version 5.0. Just replace
that version number with your desired version.

Built-in standard buildout configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. note::

The standard buildout configuration is designed to create scripts needed
for developing and testing Zope, it is not for production use. Please use
a custom buildout configuration, a minimal example is shown below.

.. code-block:: console
$ wget https://pypi.org/packages/source/Z/Zope/Zope-5.0.tar.gz
$ tar xfvz Zope-5.0.tar.gz
$ cd Zope-5.0
$ python3.7 -m venv .
$ bin/pip install -U pip wheel zc.buildout
$ bin/buildout
Custom buildout configurations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Instead of using the buildout configuration shipping with Zope itself, you
can also start with your own buildout configuration file.

The installation with a custom buildout configuration does not require you
to download Zope first:

.. code-block:: console
$ python3.7 -m venv zope
$ cd zope
<create buildout.cfg in this folder>
$ bin/pip install -U pip wheel zc.buildout
$ bin/buildout
$ python3.10 -m venv zope
$ cd zope
<create buildout.cfg in this folder, see examples below>
$ bin/pip install -U pip wheel zc.buildout
$ bin/buildout
Minimum configuration
+++++++++++++++++++++
Here's a minimum ``buildout.cfg`` configuration example:
Using the simplest possible configuration
+++++++++++++++++++++++++++++++++++++++++
Here's a minimum ``buildout.cfg`` configuration example that will create the
built-in ``bin/mkwsgiinstance`` script to create a Zope instance:

.. code-block:: ini
@@ -168,10 +145,10 @@ version you find on https://zopefoundation.github.io/Zope/:

.. code-block:: console
$ python3.7 -m venv zope
$ python3.10 -m venv zope
$ cd zope
$ bin/pip install -U pip wheel
$ bin/pip install Zope[wsgi]==5.0 \
$ bin/pip install Zope[wsgi] \
-c https://zopefoundation.github.io/Zope/releases/5.0/constraints.txt
You can also install Zope using a single requirements file. **Note that this
@@ -184,11 +161,19 @@ more than are listed in the ``install_requires`` section of ``setup.py``):
-r https://zopefoundation.github.io/Zope/releases/5.0/requirements-full.txt
Building the documentation with ``Sphinx``
------------------------------------------
If you have used ``zc.buildout`` for installation, you can build the HTML
documentation locally:
Building the documentation
--------------------------
You can build the documentation locally. Example steps on Linux. Replace the
version number "5.0" with the latest version you find on
https://zopefoundation.github.io/Zope/:

.. code-block:: console
$ bin/make-docs
$ wget https://pypi.org/packages/source/Z/Zope/Zope-5.0.tar.gz
$ tar xfz Zope-5.0.tar.gz
$ cd Zope-5.0
$ python3.10 -m venv .
$ bin/pip install -U pip wheel
$ bin/pip install Zope[docs] -c ./constraints.txt
$ cd docs
$ make html
158 changes: 17 additions & 141 deletions docs/operation.rst
Original file line number Diff line number Diff line change
@@ -8,7 +8,8 @@ Whichever method you used to install Zope and create a server instance (see
If you have installed Zope using ``zc.buildout`` in conjunction with
``plone.recipe.zope2instance`` as outlined in :doc:`INSTALL`, many of
the following tasks are already done for you and some others differ
slightly. You can immediately skip down to `Running Zope`.
slightly. You can immediately skip down to `Running Zope
(plone.recipe.zope2instance install)`.

.. contents::
:local:
@@ -26,8 +27,8 @@ Zope server process. The instance home is created using the
$ bin/mkwsgiinstance -d .
The `-d .` argument specifies the directory to create the instance
home in.
The `-d` argument specifies the directory to create the instance
home in, where the dot (``.``) means the current folder.

You will be asked to provide a user name and password for an
administrator's account during ``mkwsgiinstance``. To see all available
@@ -168,7 +169,7 @@ Now you are able to log in using a browser, as described in

Running Zope as a Daemon
~~~~~~~~~~~~~~~~~~~~~~~~
Zope itself has no built-in support for running as a daemon any more.
Zope itself has no built-in support for running as a daemon anymore.

If you create your Zope instance using ``plone.recipe.zope2instance`` you can
use its start/stop script to daemonize Zope. See the next section for how to do
@@ -504,37 +505,23 @@ A simple contrived load test was done with the following parameters:
- 100 concurrent clients accessing Zope
- 100 seconds run time
- the clients just fetch "/"
- standard Zope 4 instances, one with ZEO and one without
- Python 2.7.16 on macOS Mojave/10.14.4
- standard Zope 5.9 instances, one with ZEO and one without
- Python 3.11.7 on macOS Sonoma/14.2.1
- standard WSGI server configurations, the only changes are to number of
threads and/or number of workers where available.

This load test uncovered several issues:

- ``cheroot`` (tested version: 6.5.5) was magnitudes slower than all others.
Unlike the others, it did not max out CPU. It is unclear where the slowdown
originates. Others reached 500-750 requests/second. ``cheroot`` only served
12 requests/second per configured thread.
- ``gunicorn`` (tested version: 19.9.0) showed very strange behavior against
the non-ZEO Zope instance. It serves around 500 requests/second, but then
hangs and serves no requests for several seconds, before picking up again.
- ``gunicorn`` (tested version: 19.9.0) does not like the ZEO instance at all.
No matter what configuration in terms of threads or workers was chosen
``gunicorn`` just hung so badly that even CTRL-C would not kill it.
Switching to an asynchronous type of worker (tested with ``gevent``)
did not make a difference.
- ``werkzeug`` (tested version: 0.15.2) does not let you specify the number
of threads, you only tell it to use threads or not. In threaded mode it
spawns too many threads and immedialy runs up agains the ZODB connection
pool limits, so with Zope only the unthreaded mode is suitable. Even in
unthreaded mode, the service speed was inconsistent. Just like ``gunicorn``
it had intermittent hangs before recovering.
- ``bjoern`` (tested version: 3.0.0) is the clear speed winner with 740
requests/second against both the ZEO and non-ZEO Zope instance, even though
it is single-threaded.
- ``waitress`` (tested version: 1.3.0) is the all-around best choice. It's
just 10-15% slower than ``bjoern``, but both the built-in WSGI tools as well
as ``plone.recipe.zope2instance`` use it as the default and make it very
- ``cheroot`` (tested version: 10.0.0) seemed overwhelmed by the load. It kept
resetting connections to the test client with an error rate of about 1.5%.
- ``gunicorn`` (tested version: 19.9.0) does not work at all with ZEO. Without
ZEO it only works if a single worker is configured. Even with a single thread
client connections timed out, the failure rate was about 0.25%.
- ``bjoern`` (tested version: 3.2.2) is the clear speed winner with 3,870
requests/second against both the ZEO and non-ZEO Zope instance.
- ``waitress`` (tested version: 2.1.12) is the all-around best choice. It's
just about 15% slower than ``bjoern``, but both the built-in WSGI tools as
well as ``plone.recipe.zope2instance`` use it as the default and make it very
convenient to use.


@@ -591,117 +578,6 @@ section will pull in the correct dependencies:
wsgi = ${buildout:directory}/etc/bjoern.ini
Problematic WSGI servers
~~~~~~~~~~~~~~~~~~~~~~~~

werkzeug
++++++++
`werkzeug <https://palletsprojects.com/p/werkzeug/>`_ is a WSGI library that
contains not just a WSGI server, but also a powerful debugger. It can
easily integrate with Zope using a shim package called `dataflake.wsgi.werkzeug
<https://dataflakewsgiwerkzeug.readthedocs.io/>`_. See the `Using this package`
section for how to integrate `werkzeug` using Zope's own ``runwsgi`` script and
how to create a suitable WSGI configuration.

If you use ``plone.recipe.zope2instance``, the following section will pull in
the correct dependencies, after you have created a WSGI configuration file:

.. code-block:: ini
[zopeinstance]
recipe = plone.recipe.zope2instance
eggs =
dataflake.wsgi.werkzeug
zodb-temporary-storage = off
user = admin:password
http-address = 8080
wsgi = ${buildout:directory}/etc/werkzeug.ini
gunicorn
++++++++
The `gunicorn WSGI server <https://gunicorn.org/>`_ has a built-in
`PasteDeploy` entry point and integrates easily. The following example buildout
configuration section will create a ``bin/runwsgi`` script that uses
`gunicorn`.

.. code-block:: ini
[gunicorn]
recipe = zc.recipe.egg
eggs =
Zope
gunicorn
scripts =
runwsgi
You can use this script with a WSGI configuration file that you have to create
yourself. Please see the `gunicorn documentation
<https://docs.gunicorn.org/>`_, especially the `Configuration File` section on
`Configuration Overview`, for Paster Application configuration information. A
very simple server configuration looks like this:

.. code-block:: ini
[server:main]
use = egg:gunicorn#main
host = 192.168.0.1
port = 8080
proc_name = zope
You can then run the server using ``runwsgi``:

.. code-block:: console
$ bin/runwsgi etc/gunicorn.ini
2019-04-22 11:45:39 INFO [Zope:45][MainThread] Ready to handle requests
Starting server in PID 84983.
.. note::
gunicorn version 19.9.0 or less will print an ominous warning message on the
console upon startup that seems to suggest their WSGI entry point is
deprecated in favor of using their own built-in scripts. This is misleading.
Future versions will not show this message.

If you use ``plone.recipe.zope2instance``, you can make it use `gunicorn` by
adding its egg to the buildout section and setting the WSGI configuration file
path to the path of the configuration file you created yourself:

.. code-block:: ini
[zopeinstance]
recipe = plone.recipe.zope2instance
eggs =
gunicorn
zodb-temporary-storage = off
user = admin:password
http-address = 8080
wsgi = ${buildout:directory}/etc/gunicorn.ini
cheroot
+++++++
The `cheroot WSGI server <https://cheroot.cherrypy.org>`_ can be integrated
using a shim package called `dataflake.wsgi.cheroot
<https://dataflakewsgicheroot.readthedocs.io/>`_. See the `Using this package`
section for details on how to integrate `cheroot` using Zope's own
``runwsgi`` script and how to create a suitable WSGI configuration.

If you use ``plone.recipe.zope2instance``, the following
section will pull in the correct dependencies:

.. code-block:: ini
[zopeinstance]
recipe = plone.recipe.zope2instance
eggs =
dataflake.wsgi.cheroot
zodb-temporary-storage = off
user = admin:password
http-address = 8080
wsgi = ${buildout:directory}/etc/cheroot.ini
Debugging Zope applications under WSGI
--------------------------------------
You can debug a WSGI-based Zope application by adding a statement to activate
14 changes: 7 additions & 7 deletions src/ZPublisher/HTTPResponse.py
Original file line number Diff line number Diff line change
@@ -236,24 +236,24 @@ def redirect(self, location, status=302, lock=0):
# To be entirely correct, we must make sure that all non-ASCII
# characters are quoted correctly.
parsed = list(urlparse(location))
rfc2396_unreserved = "-_.!~*'()" # RFC 2396 section 2.3
rfc3986_unreserved = "-_.!~*'()" # RFC 3986 section 2.3
for idx, idx_safe in (
# authority
(1, ";:@?/&=+$,"), # RFC 2396 section 3.2, 3.2.1, 3.2.3
(1, "[];:@?/&=+$,"), # RFC 3986 section 3.2, 3.2.1, 3.2.3
# path
(2, "/;:@&=+$,"), # RFC 2396 section 3.3
(2, "/;:@&=+$,"), # RFC 3986 section 3.3
# params - actually part of path; empty in Python 3
(3, "/;:@&=+$,"), # RFC 2396 section 3.3
(3, "/;:@&=+$,"), # RFC 3986 section 3.3
# query
(4, ";/?:@&=+,$"), # RFC 2396 section 3.4
(4, ";/?:@&=+,$"), # RFC 3986 section 3.4
# fragment
(5, ";/?:@&=+$,"), # RFC 2396 section 4
(5, ";/?:@&=+$,"), # RFC 3986 section 4
):
# Make a hacky guess whether the component is already
# URL-encoded by checking for %. If it is, we don't touch it.
if '%' not in parsed[idx]:
parsed[idx] = quote(parsed[idx],
safe=rfc2396_unreserved + idx_safe)
safe=rfc3986_unreserved + idx_safe)
location = urlunparse(parsed)

self.setStatus(status, lock=lock)
Loading