taler-docs

Documentation for GNU Taler components, APIs and protocols
Log | Files | Refs | README | LICENSE

commit 8e7245c57ac5f31374103a0298ddbe22e2acf20f
parent d6ad9388d1e90f35b1355377630919c153a6c9e9
Author: Christian Grothoff <christian@grothoff.org>
Date:   Fri,  7 Aug 2026 19:16:18 +0200

update paivana docs

Diffstat:
Mdesign-documents/076-paywall-proxy.rst | 30+++++++++++++++++++++++++++++-
Mfrags/paivana-httpd-manual.rst | 68+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mmanpages/paivana-httpd.1.rst | 18++++++++++++------
Mmanpages/paivana.conf.5.rst | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 177 insertions(+), 8 deletions(-)

diff --git a/design-documents/076-paywall-proxy.rst b/design-documents/076-paywall-proxy.rst @@ -224,7 +224,7 @@ Problems: Accepted risks: --------------- -Three properties of this design were examined and deliberately kept as they +Four properties of this design were examined and deliberately kept as they are. Each is recorded here together with the assumption that makes it acceptable, because a deployment that does not satisfy the assumption does not get the property. @@ -336,6 +336,34 @@ chosen by a person**; where a person chooses it, the cookies are forgeable and the paywall is decorative. How to generate such a secret is operator guidance and belongs with the manual rather than here. +Truncation is unreportable to an HTTP/1.0 client +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +paivana-httpd relays bodies as they arrive rather than assembling them +first, which is what lets it serve content larger than memory and lets the +client start receiving before the upstream has finished. The cost is that +the upstream's status line and headers reach the client long before the +body is complete, so an upstream that fails mid-body cannot be reported as +``502`` — that status has already been spent. + +What is left is to break the framing, which for almost every client is +enough: a declared ``Content-Length`` is left unmet, or a chunked response +is closed without its terminating chunk, and RFC 9112 section 8.1.2 +requires a recipient to treat either as a failed message. The exception is +an HTTP/1.0 client receiving a response whose length the upstream never +declared. Such a client cannot be sent chunks, so the close of the +connection *is* the end-of-body marker, and a truncated body is +byte-for-byte indistinguishable from a complete one. + +The alternative would be to buffer each response until it is known to be +complete, which is exactly the property being given up, and which bounds +every response by memory to buy correct reporting for one obsolete client +version. **The risk is accepted on the assumption that clients speak +HTTP/1.1**, which every browser and every HTTP library in current use has +done since well before this design; where a genuine HTTP/1.0 client is +expected, the upstream should be configured to declare a +``Content-Length``, which restores detection for it too. + Implementation: --------------- diff --git a/frags/paivana-httpd-manual.rst b/frags/paivana-httpd-manual.rst @@ -286,7 +286,9 @@ like this: The exhaustive list of supported keys (``SERVE``, ``PORT``, ``BIND_TO``, ``UNIXPATH``, ``UNIXPATH_MODE``, ``CONNECTION_LIMIT``, -``PER_IP_CONNECTION_LIMIT``, ``BASE_URL``, +``PER_IP_CONNECTION_LIMIT``, ``MAX_REQUEST_SIZE``, +``REQUEST_BUFFER_MAX``, ``RESPONSE_BUFFER_MAX``, ``UPSTREAM_TIMEOUT``, +``UPSTREAM_STALL_TIMEOUT``, ``BASE_URL``, ``DESTINATION_BASE_URL``, ``DESTINATION_UNIXPATH``, ``MERCHANT_BACKEND_URL``, ``MERCHANT_BACKEND_UNIX_PATH``, ``MERCHANT_ACCESS_TOKEN``, ``SECRET``, ``WHITELIST``, ``TRUSTED_PROXIES``, @@ -322,6 +324,70 @@ a single payment per site and ``-n`` to disable the paywall for debugging). +Body sizes, buffering and upstream timeouts +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``paivana-httpd`` relays bodies as they arrive rather than assembling +them first. A request or response of any size therefore passes +through, and the client starts receiving the response while the +upstream is still producing it — neither of which was true when each +body had to fit in memory, where anything over 40 MiB failed outright +and nothing reached the client until everything had. + +Two consequences are worth knowing before tuning anything. + +**Memory is bounded by the buffers, not by the content.** An +in-flight request can hold at most ``REQUEST_BUFFER_MAX`` plus +``RESPONSE_BUFFER_MAX`` (256 KiB each by default), so the ceiling for +the process is that sum times ``CONNECTION_LIMIT`` — about 256 MiB at +the shipped defaults. That is a ceiling and not a typical cost: the +buffers are allocated on demand and grown only as far as a request +actually needs, so a page that arrives in one read never occupies more +than that read. Raise the buffers only to trade memory for throughput +on fast links, and lower ``CONNECTION_LIMIT`` if you raise them a +lot. When the client cannot keep up, Paivana stops reading +from the upstream; when the upstream cannot keep up, Paivana stops +reading from the client. Neither ever accumulates. + +**A response can now fail after its status has been sent.** Once the +upstream's headers have gone out they cannot be retracted, so an +upstream that dies mid-body can no longer be reported as ``502``. +Paivana signals it by breaking the framing instead: a declared +``Content-Length`` is left unmet, or a chunked response is closed +without its terminating chunk. Both are failures a conforming client +must detect (RFC 9112, section 8.1.2) — ``curl`` reports exit code +18, for instance — and the event is logged at ``WARNING`` naming the +URL and the byte count reached. + +.. note:: + + There is one case where it cannot be signalled at all: an HTTP/1.0 + client receiving a response whose length the upstream never + declared. There the close of the connection *is* the framing, so a + truncated body is indistinguishable from a complete one. Nothing + at this layer can fix that; it is a property of HTTP/1.0. It does + not arise for HTTP/1.1 clients, which get chunked framing, nor for + any client when the upstream declares a length. + +The two upstream timeouts bound different things and neither bounds +the request as a whole: + +- ``UPSTREAM_TIMEOUT`` (60 s) is the time to the response *headers*. + Expiry here yields ``504``, and this is the only one that can still + produce a status code. +- ``UPSTREAM_STALL_TIMEOUT`` (60 s) is how long the upstream may move + no bytes in either direction. Expiry here truncates the response as + described above, if it had started, and yields ``504`` if it had + not. The clock does not run while Paivana is holding the upstream + back because the client has not caught up, so a slow client is never + mistaken for a stalled upstream. + +Raise ``UPSTREAM_STALL_TIMEOUT`` if your upstream legitimately pauses +for long stretches mid-response — a report generator that computes for +minutes between writes, say. Do not raise it to accommodate slow +*clients*; that is already handled. + + Starting and stopping the service ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/manpages/paivana-httpd.1.rst b/manpages/paivana-httpd.1.rst @@ -100,12 +100,18 @@ Its options are as follows: reverse proxy itself. **-u** *BYTES* | **--max-upload=**\ \ *BYTES* - Set the maximum upload size allowed by the reverse proxy; the - default is 1048576. Requests with a larger body are rejected with - HTTP 413. Only applies to the body size (the HTTP header has a - separate limit). The body is buffered in memory before it is - forwarded and the implementation caps that buffer at 40 MiB, so a - value above 41943040 has no effect. + Set how many bytes of a request body are held in memory at once + while it is relayed upstream; the default is 262144. Equivalent to + ``REQUEST_BUFFER_MAX`` in the configuration file, and like it a + throughput knob rather than a limit: the largest body accepted is + ``MAX_REQUEST_SIZE``, and bodies above that are rejected with HTTP + 413. Must be between 1 and 41943040. + + This option used to set the limit, because a body that could not be + held could not be forwarded. Passing it and leaving + ``MAX_REQUEST_SIZE`` unset therefore still sets both, so an + existing invocation keeps the limit it asked for; see + paivana.conf(5). **-v** | **--version** Print version number. diff --git a/manpages/paivana.conf.5.rst b/manpages/paivana.conf.5.rst @@ -70,6 +70,75 @@ PER_IP_CONNECTION_LIMIT ``unix`` or ``systemd`` every client shares one peer, and behind a reverse proxy or a NAT many clients do. This setting is optional. +MAX_REQUEST_SIZE + Largest request body accepted, default 1048576. A body above this + is answered with HTTP 413; where the client declared a + ``Content-Length`` the refusal is immediate, and otherwise it + follows as soon as the body actually exceeds the limit. Only the + body is counted -- the request header block has its own limit. + This setting is optional. + + This is a policy limit and not a memory one. Bodies are streamed, + so the amount Paivana holds at once is ``REQUEST_BUFFER_MAX`` + regardless of how large this is. + +REQUEST_BUFFER_MAX + How many bytes of a request body Paivana holds in memory at once + while relaying it upstream, default 262144. Also settable with + **-u**; see paivana-httpd(1). Must be between 1 and 41943040. + This setting is optional. + + A throughput knob rather than a limit: it bounds how far the client + may run ahead of the upstream before Paivana stops reading from it, + not how large a body may be. Larger means fewer suspend/resume + round trips on a fast link and more memory per request in flight; + the worst case is this times ``CONNECTION_LIMIT``. + + Before request bodies were streamed this key did both jobs, because + a body that could not be held could not be forwarded. For the sake + of configurations written then, setting this and **not** setting + ``MAX_REQUEST_SIZE`` still sets both: such a file keeps the limit it + asked for, at the cost of a correspondingly large buffer. Set both + explicitly to separate them. + +RESPONSE_BUFFER_MAX + As ``REQUEST_BUFFER_MAX``, for the response body travelling the + other way, default 262144. This setting is optional. + + There is deliberately no ``MAX_RESPONSE_SIZE`` to go with it. A + response is relayed as it arrives and never assembled, so its size + is bounded by nothing in Paivana; an operator who wants a ceiling on + what their own upstream serves can impose it at the upstream. + +UPSTREAM_TIMEOUT + How long the upstream has to produce its response *headers* before + the request is answered with HTTP 504, default ``60 s``. This + setting is optional. + + Not a bound on how long a request may take: a large download runs + for as long as it runs. The clock stops the moment the header + section ends, and this is the only one of the two upstream timeouts + that can still produce a status code -- after it, the status has + already gone to the client. A stalling upstream is caught by + ``UPSTREAM_STALL_TIMEOUT`` instead. + +UPSTREAM_STALL_TIMEOUT + How long the upstream may move no bytes at all, in either + direction, before Paivana gives up on the request, default + ``60 s``. This setting is optional. + + The clock does not run while Paivana is itself the reason nothing is + moving -- that is, while it has stopped reading from the upstream + because the client has not drained what already arrived. A client + on a slow link is therefore never mistaken for a slow upstream. + + What expiry does to the client depends on how far the response had + got. Before the upstream's headers were relayed, the client gets + HTTP 504. After them the status has already been sent and cannot be + retracted, so the response is truncated instead: a declared + ``Content-Length`` is left unmet, or a chunked response is closed + without its terminating chunk. The Paivana manual has the details. + TRUSTED_PROXIES IPv4 networks whose members are reverse proxies trusted to report the client address in the ``Forwarded`` or ``X-Forwarded-For``